Pin Change Interrupt Tutorial?

Go To Last Post
25 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Does anyone know if there is a tutorial on interrupts for Pin Change? I have a controller I designed that has several photo sensors and magnetic sensors that control two outputs. Those sensors can change at any time, it is completely random, so it would make sense to run segments of my code based on the a pin change, but I have no idea how to actually use a pin change interrupt and I was not able to find anything in the tutorials for IO. If I could see a simple example of the following psudo code/sequence using pin change interrupt I think I could mostly take it from there.

Pin X goes high
Output x goes high

Last Edited: Wed. Aug 23, 2017 - 07:31 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The Pin Change interrupt is a compromise in the design of the AVR between having the capability of independently-vectored interrupts for the signals on every I/O pin, and having leaving some parts of the Register I/O space available for things other than managing the enabling, flagging, and mode-selection for all those interrupts, and reserving an enormous chunk of program space for all the vectors that "full capability" would imply.

The Pin Change interrupt involves just ONE interrupt vector that's branched to when any combination of the pins that have "state change" detection built into them change state. In exchange for the large savings in interrupt vector space, it becomes the responsibility of the single PinChange interrupt service routine to determine WHICH pins changed, and in what direction (although sometimes you don't really care about this)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Using a mega48/88/168 as an example:
Set the bits of the appropriate PCMSKx register high to enable pin change detection on that pin. For PD2 (PCINT18) that would be:

PCMSK2 |= (1<<PCINT18);

Then enable the interrupt for the corresponding vector and of course the global interrupt flag:

PCICR |= (1<<PCIE2);
sei();

The provide the ISR for that vector that does what you need. For avr-gcc this would look like this:

ISR(PCINT2_vect)
{
    // Your code here
}

For one pin (or more generally, one pin per PCINT vector), that is all you really need. If you have more that one pin per vector enabled, then as Levenkay says, you have to have code in the ISR that determines which of the pins caused the change. This can be done by keeping a variable that stores the value of the register the last time the ISR was run and comparing to the current value. There can be problems with timing issues, however, since by the time you get to the ISR, another pin may have changed (or even worse, the one that did change changed back). These can be difficult to deal with, but how serious a problem it is depends on the characteristics of your application.

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just reviving this,

Quote:
// Your code here

Quote:
another pin may have changed (or even worse, the one that did change changed back).

Does anyone have sample code (ASM) to handle this?

I have written something along the lines of the above (with a variable holding the last value etc) but the near simultaneous change or fast change back may bring it unstuck.

Scattered showers my arse -- Noah, 2348BC.
Rob Gray, old fart, nature photographer, embedded hardware/software designer, and serial motorhome builder, www.robgray.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Does anyone have sample code (ASM) to handle this?

You'd be surprised how close C is to Asm for things like this:

PCMSK2 |= (1<<PCINT18); 

becomes

ldi r24, (1<<PCINT18)
sts PCMSK2, r24
PCICR |= (1<<PCIE2); 
sei();

becomes:

ldi r24, (1<<PCIE2)
sts PCICR, r24
sei

For the ISR first fill in the PCINT2 vector down near 0:

  .org 0 
  rjmp reset
  .org PCI2addr
  rjmp PCI2_ISR

reset:
  ...


PCI2_ISR:
  ; your code here
  reti

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

First if it change back you will never know (but then it has been very short).
What speed to we really talk about?

The way I often do it is by haveing a fast timer interrupt that read the port and compare to see if it has changed since last time, if not fast out again.(In ASM that can be done in 12 clk(10clk in dirty code)).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@clawson
Yes at this low level the C and ASM are very similar.

I can't do the fast timer trick because I have too much else to do as well and that would tie up the processor most of the time for no result.

However I've had a think and the "fast pulse" is not a problem, that will be a glitch which will be ignored.

As for the speed, I'm bit banging the Rx and Tx of two asynch serial lines at the same time at up to 20kHz but most likely 9600 or even 4800. The two signals however are totally independant and therefore edges could arrive at any time.

I think I've got it pretty much sorted (as a design, no coding yet) but Koshchi's comment got me wondering about the situation when two edges arrive seperated by a very short period, say < the time to service the interrupt and read the port, eg

IP1 changes state
  ISR entered and PCIF cleared
  read port
  IP2 changes state
  do detect logic based on only IP1
  exit ISR
execute a single instruction then
 ISR entered again for IP2

I think this works.

Scattered showers my arse -- Noah, 2348BC.
Rob Gray, old fart, nature photographer, embedded hardware/software designer, and serial motorhome builder, www.robgray.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Now I'm confused how will you make 9600 baud (4800) in software without using a timer?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Sorry for the confusion, I do need a timer for generating the bits and for testing the level at the 50% mark when receiving them, I'm only talking about detecting the incoming bit edges here.

Scattered showers my arse -- Noah, 2348BC.
Rob Gray, old fart, nature photographer, embedded hardware/software designer, and serial motorhome builder, www.robgray.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

But that stille make me confused, bacause you say that Tx an Rx is asyn. to each other, so does that mean that you use 2 timers? If that's the case you could have a interrupt delay problem when you deal with two bit at (close) same time!
And again I have used a timer interrupt at 5x the baud rate and a state mashine, it waste a lot of time when nothing happens but do you care? (@9600 baud on 16MHz it's about 4% of the time)The real deal is can it keep up when you Rx and Tx streams (100% load).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Sparrow2,

I appreciate your time but I think I've exceeded my ability to explain what I want to do with words.

Attached is a block diagram, there are two data streams NRX->ARX and ATX->NTX.

ATX->NTX is async chars but the chip doesn't care, it basically just transfers the ATX pin levels through to NTX but does apply some tests on the data like "has it been LOW for too long?"

NRX->ARX has to assemble the chars received on the NRX pin and buffer them for later transmission on the ARX pin.

I can flow control the ATX data stream by dropping CTS/ERR but have no control over the incoming data on NRX.

And just to make it more interesting I have to bit rate detect both data streams as they could be anything within a range as yet to be determined and different to each other. I also have to detect bus clashes on the NTX/NTX pair (these go to a line transceiver).

Anyway this is probably getting too much for a thread here and I don't expect you to spend hours diagnosing my design.

I do however like the idea of running a fast timer interrupt and will look into that.

As to whether I can keep up with constant data, for the time being that is in the lap of the Gods.

Rob

Attachment(s): 

Scattered showers my arse -- Noah, 2348BC.
Rob Gray, old fart, nature photographer, embedded hardware/software designer, and serial motorhome builder, www.robgray.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think that you should make some flow diagrams before anything else ;)
How to recover.
Protocol, incl break/reset.
When you say you don't know the speed look into manchester code, if it has to be RS232/485 like look into a small software DDS for speed.
If you in any way can define a master and slave the flow and timing will much more clear, even if means that more data needs to flow.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks sparrow, I'd forgotten about manchester, that would solve the clock recovery problem. As for the 485 look alike I'm using whatever transcievers make sense so they will handle the actual line levels.

Anyway I've obviously still got a lot of design work to do, good thing that's the fun part.

Scattered showers my arse -- Noah, 2348BC.
Rob Gray, old fart, nature photographer, embedded hardware/software designer, and serial motorhome builder, www.robgray.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi All,
I have write code for PCINT0 pin. But when I push a button to change that pin. It did not generate any interrupt.This is my code. Can you please run it for me on ATmega1281 micro controller.

#include
#include
#include

int main(void)
{

DDRB=0b11110000;//input
DDRC=0xFF;//Output
PCICR=(1<<PCIE0);//For PIN CHANGE interrupt
PCIFR=(1<<PCIF0);//Interrupt flag is set for
PCMSK0=(1<<PCINT0);//Enable interrupt PCINT0
sei();
}

ISR(PCINT0_vect)
{
PORTC=0x55;
_delay_ms(10);
PORTC=0x00;
}

which i use push button on PB0 , It did not execute ISR. Please explain

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

inner_ideas wrote:
Hi All,
I have write code for PCINT0 pin. But when I push a button to change that pin. It did not generate any interrupt.This is my code. Can you please run it for me on ATmega1281 micro controller.

#include 
#include 
#include 

int main(void)
{
		
	DDRB=0b11110000;//input
	DDRC=0xFF;//Output
        PCICR=(1<<PCIE0);//For PIN CHANGE interrupt
	PCIFR=(1<<PCIF0);//Interrupt flag is set for 
	PCMSK0=(1<<PCINT0);//Enable interrupt PCINT0 
	sei();	
}

ISR(PCINT0_vect)
{
PORTC=0x55;
_delay_ms(10);
PORTC=0x00;
}

which i use push button on PB0 , It did not execute ISR. Please explain


Life is much easier if you use CODE tags. I can't alter your post, but I can add CODE tags when I quote you.

Whenever you use external interrupts, you need to use pull-up or pull-down on any input pin. Otherwise any noise will trigger the interrupt.

Since the AVR has the ability to provide 'internal pull-up', this is cheaper than buying a whole external resistor.

Otherwise, your program looks as if it should 'work'. Note that it is unwise to put delays inside an ISR().

Ah-ha. You have omitted a while(1) loop at the bottom of main().
Without this you 'fall off the bottom'. GCC actually switches off interrupts with a CLI and provides its own 'here: RJMP here' endless loop.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ISR(PCINT0_vect)
{
PORTC=0x55;
_delay_ms(10); // 10 ms
PORTC=0x00;
}

How do you detect the interrupt is serviced? Is 1/100 s enough for a human (brain, eye)?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you for coordinating on my post. This is my updated code. I want to use multiple PCINT. How I can detect that which pin generate a specific interrupt. I am trying to simulate but not succeeded yet. This is code

/*
 * INTTTRRUPT.c
 *
 * Created: 4/25/2013 3:15:13 PM
 *  Author: Administrator
 */ 
#include 
#include 
#include 

uint8_t i;

int main(void)
{

DDRB=0b11110000;//input
DDRC=0xFF;//Output
PCIFR=(0<<PCIF0);//Interrupt flag is set for
PCICR=(1<<PCIE0);//For PIN CHANGE interrupt
sei();
//PCMSK0=(1<<PCINT0)|(1<<PCINT1)|(1<<PCINT2);//Enable interrupt PCINT0

while(1)
{
	switch(i)
	{
		
	
case 0:
PORTC=0b00000001;
_delay_ms(50);
PORTC=0x00;
break;
	
case 1:
PORTC=0b00000010;
_delay_ms(50);
PORTC=0x00;
break;
	
case 2:
PORTC=0b00000100;
_delay_ms(50);
PORTC=0x00;
break;
	
case 3:
PORTC=0b00001000;
_delay_ms(50);
PORTC=0x00;
break;
}

}
}

ISR(PCINT0_vect)
{
if(PCMSK0|=0x01)
i=1;
else if(PCMSK0|=0x02)
i=2;
else if(PCMSK0|=0x04)
i=3;
else i=4;
PCIFR=(1<<PCIF0);//Interrupt flag is set for
}

[code tags added - please learn to do this yourself in future - moderator]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I edited your post to added code tags and the fact that there's just a tiny bit of indentation suggests you may have actually written the code as above with almost everything flush against the left margin. It makes the code almost impossible to follow but there are some things in that I can still spot as obvious errors:

PCIFR=(0<<PCIF0);//Interrupt flag is set for 

Not sure what you were trying to achieve here but it almost certainly does not do what you hoped!

uint8_t i; 

i (great name for a variable by the way!) is shared between the ISR and main() therefore it MUST be volatile.

//PCMSK0=(1<<PCINT0)|(1<<PCINT1)|(1<<PCINT2);//Enable interrupt PCINT0

As this line is commented then you are never going to get an interrupt anyway are you?

if(PCMSK0|=0x01)
i=1;
else if(PCMSK0|=0x02)
i=2;
else if(PCMSK0|=0x04)
i=3;
else i=4; 

that is not the way you determine which pin interrupted. You need to read the associated PIN register and determine what has changed since the last interrupt (XOR is a good way to spot what changed by the way).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi great help. I have tried to remove those errors. When I am simulating it in Proteous. Its still not working.

#include 
#include 
#include 

volatile uint8_t i;

int main(void)
{

DDRB=0b11110000;//input
DDRC=0xFF;//Output
PCIFR=(1<<PCIF0);//Interrupt flag is set for
PCICR=(1<<PCIE0);//For PIN CHANGE interrupt
sei();
PCMSK0=(1<<PCINT0)|(1<<PCINT1)|(1<<PCINT2);//Enable interrupt PCINT0

while(1)
{
	switch(i)
	{
		
	
case 0:
PORTC=0b00000001;
_delay_ms(50);
PORTC=0x00;
break;
	
case 1:
PORTC=0b00000010;
_delay_ms(50);
PORTC=0x00;
break;
	
case 2:
PORTC=0b00000100;
_delay_ms(50);
PORTC=0x00;
break;
	
case 3:
PORTC=0b00001000;
_delay_ms(50);
PORTC=0x00;
break;
}
i=4;
}
}

ISR(PCINT0_vect)
{
if(PORTB^=0x01)
i=1;
else if(PORTB^=0x02)
i=2;
else if(PORTB^=0x04)
i=3;
else i=4;

}

[code tags added for you yet again - my patience is running low - PLEASE learn to do this yourself in future. You just paste the code, then highlight it (if not already) then click the [ code ] button at the top of the editor - moderator]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

PINB not PORTB and the ^= should be against it's previous state (hold it in a "static")

also do you really type your code like that? Surely your keyboard has a [Tab] key?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, this does not change anything to the behavior, but your series of cases (i=0,1,2,3) where you set the i-th bit could be replaced by -more concise;- :

if (i < 4) { // I would avoid giving vars such names as i, as they are often used for loops...
  PORTC=(1<<i);
  delay_ms(50);
  PORTC=0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
my patience is running low - PLEASE learn to do this yourself in future.

https://www.avrfreaks.net/index.p...

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi,
Thank you its solved.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

hello friends,

I am using atmega32 and trying to simulate something related with LCD, Buttons, LEDS.

I'm trying to use LCD and LED simoultaneously like whenever i press a button (using 4 buttons) LCD display my data and LED cycle ain't got interrupted(disturbed). And i tried doing that but unfortunately if LCD display data my LED cycle stops and if LED cycles Continues my LCD ain't display anything...screen of lcd freezed.

hope you guy's understand my problem.

please help.

Manish verma

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The thread is 4 YO and you have another thread on the same thing.

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

Topic locked