Chapter 6: External Interrupts & Input Capture¶
Section 1: What Are External Interrupts?¶
External interrupts allow your microcontroller to react immediately to changes on specific input pins, such as when a button is pressed, a sensor detects motion, or a digital signal changes state.
How They Work¶
External interrupts are triggered by electrical transitions on dedicated interrupt pins:
- Rising edge → when the signal goes from LOW (0) to HIGH (1)
- Falling edge → when the signal goes from HIGH (1) to LOW (0)
- Some systems allow both edges to trigger an interrupt
When such a change is detected, the microcontroller pauses its current task, runs a specific function called an Interrupt Service Routine (ISR), then returns to what it was doing — all within a few cycles.
Hardware Lines: INTx¶
The PIC24 provides dedicated external interrupt inputs:
- INT0
is fixed to a specific pin and always uses rising edge detection
- INT1
, INT2
, etc. are configurable: you can choose rising or falling edge
- Each has its own flag (IFSx
), enable bit (IECx
), and priority level (IPCx
)
Common Use Cases¶
- Detecting a button press (toggle an LED, start/stop a timer)
- Triggering logic when an object breaks a beam sensor
- Starting an event when a signal changes state
- Reading a pulse train from another digital system
External interrupts allow your microcontroller to be responsive without constant polling — saving power and processing time.
In the next section, we’ll show how to configure these interrupts on the PIC24 and respond to real-world input like a button press.
Section 2: Configuring External Interrupts (INTx)¶
To use an external interrupt on the PIC24, you'll need to configure three main things:
- Edge sensitivity — rising or falling
- Enable the interrupt
- Clear the interrupt flag in your ISR
🔹 Basic Setup: INT1¶
Here’s how to configure INT1 to trigger on a falling edge (e.g. button press):
TRISBbits.TRISB7 = 1; // Set RB7 as input
INTCON2bits.INT1EP = 1; // 1 = falling edge, 0 = rising edge
IFS1bits.INT1IF = 0; // Clear interrupt flag
IEC1bits.INT1IE = 1; // Enable INT1 interrupt
And the corresponding ISR:
void __attribute__((__interrupt__, auto_psv)) _INT1Interrupt(void) {
IFS1bits.INT1IF = 0; // Clear the interrupt flag
LATBbits.LATB0 = 1; // Toggle LED (for example)
}
Register Summary for INT1¶
Register | Purpose |
---|---|
INTCON2bits.INT1EP |
1 = falling edge, 0 = rising edge |
IEC1bits.INT1IE |
Enable bit for INT1 |
IFS1bits.INT1IF |
Interrupt flag for INT1 |
IPC5bits.INT1IP |
Priority level (optional) |
Example: Toggle LED on Button Press¶
You can connect a button between RB7
and GND. When pressed, the line goes LOW, triggering the falling edge interrupt. The ISR toggles an LED on RB0
.
Remember to include a pull-up resistor (internal or external) to hold
RB7
HIGH when the button is not pressed.
Quick Note: Pull-Up Resistors¶
When using an external interrupt triggered by a button, the pin needs to have a known voltage level when the button is not pressed. Otherwise, it may float unpredictably and trigger false interrupts.
A pull-up resistor holds the line HIGH by default. When the button is pressed, it pulls the line LOW — creating a clean falling edge.
You can use:
- An external pull-up resistor (e.g., 10kΩ to Vdd), or
- Enable the internal pull-up (on some PIC24 devices via CNPUx
register)
This ensures your interrupt only fires when the button is intentionally pressed.
Next, we’ll explore Input Capture, a different kind of input interrupt used for measuring timing of digital signals.
Section 3: What Is Input Capture?¶
While external interrupts help detect when an event occurs, input capture helps you measure how long something took or when exactly it happened — with precision.
Input capture is used to record the exact timer value at the moment an external signal changes. This allows you to measure things like:
- The time between two button presses
- The frequency of a square wave
- The pulse width of a PWM signal
Why Use Input Capture?¶
Input capture modules are tightly linked to timers. They "capture" the current timer count and store it in a buffer the moment a specified edge occurs.
This enables precise time stamping without needing to poll the input manually.
Key Use Cases¶
- Pulse width measurement (how long a signal stayed high or low)
- Frequency detection (time between rising edges)
- Reaction timing (time between stimulus and response)
- Detecting variable pulse signals like servo control or ultrasonic sensors
Input Capture vs External Interrupts¶
Feature | External Interrupt | Input Capture |
---|---|---|
Purpose | Trigger code | Record time of event |
Edge Response | ISR runs on edge | Timer value recorded |
Useful For | Logic control | Precision timing |
CPU Involvement | Immediate ISR | Minimal — buffered |
Use input capture when you're more interested in when something happened than what should happen immediately.
In the next section, we’ll walk through how to configure and use input capture modules on the PIC24.
Section 4: Setting Up Input Capture (ICx)¶
To use Input Capture on the PIC24, you'll configure one of the ICx modules (e.g., IC1, IC2) to record the timer value when a digital signal changes on a designated pin.
The capture value is automatically placed into a buffer, which you can read later in software or inside an ISR.
🔹 Basic Configuration Steps¶
- Configure the pin as an input
- Select the timer source (usually TMR2 or TMR3)
- Set the edge mode (rising, falling, or every edge)
- Enable interrupts (optional, but common)
- Read the captured value from the ICx buffer
Code Example: Measure Time Between Pulses¶
// Setup for IC1 using Timer2
IC1CON = 0
T2CON = 0 //Set the configuration to 0 as good practice
TRISBbits.TRISB2 = 1; // Set RB2 (IC1 input) as input
T2CONbits.TCKPS = 2; // Prescaler 1:64
TMR2 = 0;
PR2 = 0xFFFF;
T2CONbits.TON = 1; // Turn on Timer2
IC1CONbits.ICM = 1; // Capture on every rising edge
IC1CONbits.ICTMR = 1; // Use Timer2 as time base
IC1CONbits.ICI = 0; // Interrupt on every capture
IC1CONbits.ON = 1; // Enable Input Capture
IFS0bits.IC1IF = 0; // Clear interrupt flag
IEC0bits.IC1IE = 1; // Enable interrupt
// ISR to read captured time
void __attribute__((__interrupt__, auto_psv)) _IC1Interrupt(void) {
IFS0bits.IC1IF = 0; // Clear interrupt flag
uint16_t time = IC1BUF; // Read captured timer value
}
Edge Detection Options¶
Mode Value | Trigger Condition |
---|---|
1 | Every rising edge |
2 | Every falling edge |
3 | Every edge (rising + falling) |
0 | Module disabled |
Set using: ICxCONbits.ICM = value;
You can change the capture mode on the fly if you want to capture both rising and falling edges in sequence.
🎯 Interactive Input Capture Simulation¶
To deepen your understanding of how Input Capture modules operate, interact with the simulation below.
Experiment with different pulse durations and observe how timer values are recorded at the rising edge of a signal.
- Press Start Pulse to simulate a rising edge event.
- Adjust the Pulse Duration using the slider before starting the pulse.
- Watch how the captured timer value changes based on pulse width!
👉 Launch the Input Capture Simulation
In the next section, we’ll summarize the difference between external interrupts and input capture, and wrap up with tips and use cases.
Section 5: Summary and Best Practices¶
External interrupts and input capture give your microcontroller the ability to react to real-world events and measure their timing precisely — both essential tools in embedded systems.
Key Takeaways¶
- External Interrupts (INTx):
- Trigger an ISR when a pin changes (rising/falling edge)
- Great for reacting to button presses or logic events
-
Must clear the interrupt flag inside the ISR
-
Input Capture (ICx):
- Records the timer value when a pin changes
- Perfect for measuring pulse width, frequency, or time between events
-
Doesn’t require logic inside the ISR (just read
ICxBUF
) -
Both can be configured for rising, falling, or both edges
Best Practices¶
- ✔️ Use pull-up or pull-down resistors to ensure clean digital signals
- ✔️ Debounce buttons (in software or hardware) to avoid multiple triggers
- ✔️ Clear flags inside the ISR:
IFSxbits.INTxIF
orICxIF
- ✔️ Use input capture when timing is more important than triggering behavior
- ❌ Don’t read
ICxBUF
unlessICxIF
is set — you could get junk data
---¶
Note: Peripheral Pin Mapping (PPS)¶
On PIC24 devices with remappable pins, you must use Peripheral Pin Select (PPS) to assign Input Capture (ICx), Output Compare (OCx), UART, and other modules to specific physical pins.
For example, to map IC1 to RP7 (e.g., RB7):
__builtin_write_OSCCONL(OSCCON & 0xbf); // Unlock PPS
RPINR7bits.IC1R = 7; // IC1 input = RP7
__builtin_write_OSCCONL(OSCCON | 0x40); // Lock PPS
Skipping this step may cause ICx or OCx modules to appear non-functional.
Always refer to your microcontroller’s datasheet or family reference manual to find valid RPx mappings for your device.
Next, we’ll explore how to generate waveforms and control power using output compare and PWM in Chapter 7!
Quiz: External Interrupts & Input Capture¶
What does the input capture module do when it detects a rising edge on its input pin?
IC1CONbits.ICM = 1; // Capture on rising edge
- Immediately jumps to an interrupt
- Saves the value of PRx
- Saves the value of TMRx into IC1BUF
- Resets the timer to zero
Show Answer
The correct answer is C.
The input capture module stores the current value of the timer (usually TMR2 or TMR3) into IC1BUF
when the rising edge occurs.
This lets you determine exactly when the signal changed — great for measuring duration, frequency, or spacing between pulses.
Prompt Practice¶
Write code that configures Input Capture 1 (IC1) to capture the time of every falling edge on pin RP7, using Timer2 as the time base.
Assume you’ve already configured TMR2
with an appropriate prescaler.
Click to show solution
// Set up pin mapping: IC1 on RP7 (RB7)
__builtin_write_OSCCONL(OSCCON & 0xbf); // Unlock PPS
RPINR7bits.IC1R = 7; // Map IC1 input to RP7
__builtin_write_OSCCONL(OSCCON | 0x40); // Lock PPS
// Configure IC1
TRISBbits.TRISB7 = 1; // RB7 as input
IC1CONbits.ICTMR = 1; // Use Timer2 as time base
IC1CONbits.ICM = 2; // Capture on falling edge
IC1CONbits.ICI = 0; // Interrupt on every event
IC1CONbits.ON = 1; // Turn on IC1
IFS0bits.IC1IF = 0; // Clear interrupt flag
IEC0bits.IC1IE = 1; // Enable interrupt
// IC1 ISR
void __attribute__((__interrupt__, auto_psv)) _IC1Interrupt(void) {
uint16_t timeStamp = IC1BUF; // Read captured time
IFS0bits.IC1IF = 0; // Clear flag
}