Unit 3: PIC Interrupts & Interfacing - I
PIC Interrupts
Interrupts are one of the most important features of microcontrollers. They allow the processor to respond to asynchronous events, such as external hardware signals or internal timers, without having to continuously poll for the status of these events. This improves efficiency and reduces the CPU's workload.
Interrupt vs Polling
Polling is a technique where the microcontroller continuously checks the status of an input device or flag to see if an event has occurred. This process consumes a lot of CPU time and is inefficient for real-time applications.
On the other hand, Interrupts are signals that automatically divert the microcontroller's attention to a higher priority task. Instead of continuously checking, the microcontroller waits for an interrupt signal, which then triggers an interrupt service routine (ISR) to handle the event.
Advantages of Interrupts:
- Efficiency: The CPU is free to perform other tasks while waiting for the interrupt signal, improving multitasking capabilities.
- Real-Time Response: The system can immediately respond to critical events.
- Low Power: The microcontroller can remain in low-power mode until an interrupt occurs.
Disadvantages of Interrupts:
- Complexity: Handling multiple interrupts with different priorities can be complex.
- Timing: If interrupts occur frequently, they may affect the timing of the main program.
Interrupt Vector Table (IVT)
In PIC microcontrollers, the Interrupt Vector Table (IVT) is a predefined memory location that stores the addresses of the interrupt service routines (ISRs). When an interrupt occurs, the microcontroller looks up the IVT to find the ISR associated with the interrupt and executes it.
For example, in PIC18F series microcontrollers, the IVT is located at memory address 0x0008.
Steps in Executing an Interrupt
- Event Occurs: An interrupt event, such as a timer overflow or an external signal, occurs.
- Interrupt Detection: The microcontroller detects the event and pauses the current task.
- Save Context: The current state of the CPU (registers, program counter, etc.) is saved so that the program can resume from the same point later.
- Execute ISR: The microcontroller jumps to the address stored in the Interrupt Vector Table and executes the ISR.
- Restore Context: After the ISR is complete, the microcontroller restores the saved state.
- Resume Execution: The microcontroller resumes the main program from where it was interrupted.
Sources of Interrupts
PIC microcontrollers can generate interrupts from various sources, which include:
- External Interrupts: Generated by external devices connected to the microcontroller's pins (e.g., button press).
- Timer Interrupts: Generated when a timer overflows or reaches a specific count.
- Serial Communication Interrupts: Triggered during UART, SPI, or I2C communication events.
- ADC Interrupts: Triggered when an Analog-to-Digital Conversion (ADC) is complete.
- Watchdog Timer Interrupt: Generated when the watchdog timer overflows, typically used for system resets.
- Peripheral Interrupts: Interrupts from peripherals like PWM or comparator modules.
Enabling and Disabling Interrupts
To control interrupts, we need to enable or disable them at specific times.
-
Global Interrupt Enable (GIE): Controls whether all interrupts are enabled or disabled.
INTCONbits.GIE = 1;
// Enable all interrupts.INTCONbits.GIE = 0;
// Disable all interrupts.
-
Peripheral Interrupt Enable (PEIE): Controls whether peripheral interrupts are enabled.
INTCONbits.PEIE = 1;
// Enable peripheral interrupts.INTCONbits.PEIE = 0;
// Disable peripheral interrupts.
-
Specific Interrupt Enable Bits: Each interrupt source has its own enable bit.
- Example:
INTCONbits.TMR0IE = 1;
// Enable Timer0 interrupt.
- Example:
Interrupt Registers
PIC microcontrollers use several registers to control and manage interrupts. The most important interrupt control registers are:
-
INTCON (Interrupt Control Register): Controls the global and specific interrupts (e.g., Timer0, External interrupts).
INTCONbits.GIE
: Global Interrupt Enable bit.INTCONbits.TMR0IE
: Timer0 Interrupt Enable bit.INTCONbits.INT0IE
: External Interrupt 0 Enable bit.
-
PIE (Peripheral Interrupt Enable Register): Controls the peripheral interrupts like UART, SPI, and ADC.
PIE1bits.TXIE
: UART Transmit Interrupt Enable bit.PIE2bits.CCP1IE
: Capture/Compare/PWM Interrupt Enable bit.
-
PIR (Peripheral Interrupt Request Register): Holds the interrupt flags for peripheral modules. A flag is set when an interrupt occurs, and cleared when the ISR is executed.
PIR1bits.TXIF
: UART Transmit Interrupt Flag bit.PIR2bits.CCP1IF
: Capture/Compare/PWM Interrupt Flag bit.
Priority of Interrupts
PIC microcontrollers offer interrupt prioritization. Interrupts can be classified as high-priority or low-priority. When two interrupts occur at the same time, the higher-priority interrupt is serviced first.
- High-priority interrupts have their own vector located at address 0x0008.
- Low-priority interrupts vector to address 0x0018.
High-priority interrupts can interrupt lower-priority ones, but low-priority interrupts cannot interrupt high-priority ones.
Programming Interrupts
Timer Using Interrupts
Timers are one of the most frequently used peripherals in PIC microcontrollers, and they often generate interrupts when they overflow or reach a certain value.
Example: Timer0 Interrupt Setup
#include <xc.h>
void __interrupt() ISR() {
if (INTCONbits.TMR0IF) { // Check if Timer0 caused the interrupt
INTCONbits.TMR0IF = 0; // Clear Timer0 Interrupt Flag
PORTB ^= 0xFF; // Toggle PORTB LEDs
TMR0 = 6; // Reload Timer0 for a 1ms delay
}
}
void main() {
TRISB = 0x00; // Set PORTB as output
T0CON = 0x07; // Timer0 with 1:256 prescaler
TMR0 = 6; // Load initial value
INTCONbits.TMR0IE = 1; // Enable Timer0 interrupt
INTCONbits.GIE = 1; // Enable global interrupts
while (1) {
// Main loop remains empty; Timer0 ISR handles the task
}
}
External Hardware Interrupt
External hardware interrupts are generated by events occurring on external pins. The most common external interrupt is triggered by a button press or sensor signal.
Example: External Interrupt on RB0 (INT0 pin)
#include <xc.h>
void __interrupt() ISR() {
if (INTCONbits.INT0IF) { // Check if INT0 caused the interrupt
INTCONbits.INT0IF = 0; // Clear INT0 Interrupt Flag
PORTB ^= 0xFF; // Toggle PORTB LEDs
}
}
void main() {
TRISB = 0x00; // Set PORTB as output
INTCONbits.INT0IE = 1; // Enable external interrupt on RB0
INTCONbits.GIE = 1; // Enable global interrupts
while (1) {
// Main loop remains empty; INT0 ISR handles the task
}
}
Serial Communication Interrupt
PIC microcontrollers can generate interrupts for serial communication events such as transmitting or receiving data via UART.
Example: UART Transmit Interrupt
#include <xc.h>
void __interrupt() ISR() {
if (PIR1bits.TXIF) { // Check if UART Transmit Interrupt occurred
TXREG = 'A'; // Send a character 'A' via UART
PIR1bits.TXIF = 0; // Clear UART Transmit Interrupt Flag
}
}
void main() {
TXSTAbits.TXEN = 1; // Enable UART Transmit
PIE1bits.TXIE = 1; // Enable UART Transmit Interrupt
INTCONbits.GIE = 1; // Enable global interrupts
while (1) {
// Main loop remains empty; UART ISR handles the task
}
}
Interfacing Devices with PIC
Interfacing an LED
LEDs are simple
output devices that can be controlled by the microcontroller to indicate the status of the system or to provide visual feedback.
Example: Blinking an LED on PORTB pin RB0
#include <xc.h>
void main() {
TRISBbits.TRISB0 = 0; // Set RB0 as output
while(1) {
LATBbits.LATB0 = 1; // Turn ON LED
__delay_ms(500); // 500ms delay
LATBbits.LATB0 = 0; // Turn OFF LED
__delay_ms(500); // 500ms delay
}
}
Interfacing a 16x2 LCD (8-bit mode)
An LCD is a popular output device used in embedded systems to display information to the user. The 16x2 LCD can display 16 characters on 2 lines.
Pin Connection:
- Data pins (D0-D7) connected to PORTD.
- Control pins (RS, RW, E) connected to PORTC.
Example: Initializing and Writing to a 16x2 LCD
#include <xc.h>
#define RS LATCbits.LATC0
#define RW LATCbits.LATC1
#define E LATCbits.LATC2
void LCD_Command(unsigned char cmd) {
PORTD = cmd; // Send command to data port
RS = 0; // Set RS = 0 for command
RW = 0; // Set RW = 0 for write
E = 1; // Enable the LCD
__delay_ms(2);
E = 0; // Disable the LCD
}
void LCD_Char(unsigned char data) {
PORTD = data; // Send data to data port
RS = 1; // Set RS = 1 for data
RW = 0; // Set RW = 0 for write
E = 1; // Enable the LCD
__delay_ms(2);
E = 0; // Disable the LCD
}
void LCD_Init() {
TRISD = 0x00; // Set PORTD as output
TRISC = 0x00; // Set PORTC as output
LCD_Command(0x38); // Initialize LCD in 8-bit mode
LCD_Command(0x0C); // Display ON, Cursor OFF
LCD_Command(0x06); // Increment cursor
LCD_Command(0x01); // Clear display
__delay_ms(2);
}
void LCD_String(const char* str) {
while(*str) {
LCD_Char(*str++);
}
}
void main() {
LCD_Init(); // Initialize LCD
LCD_String("Hello"); // Display "Hello" on the LCD
while(1);
}
Interfacing a 4x4 Matrix Keyboard
A 4x4 matrix keyboard consists of 16 keys arranged in a 4-row by 4-column grid. To scan the keyboard, we activate each row one by one and check which column line is active.
Pin Connection:
- Rows (R1-R4) connected to PORTC.
- Columns (C1-C4) connected to PORTB.
Example: Reading a 4x4 Matrix Keyboard
#include <xc.h>
char Read_Keypad() {
PORTC = 0x0E; // Activate Row 1
if (PORTBbits.RB0 == 0) return '1';
if (PORTBbits.RB1 == 0) return '2';
if (PORTBbits.RB2 == 0) return '3';
if (PORTBbits.RB3 == 0) return 'A';
PORTC = 0x0D; // Activate Row 2
if (PORTBbits.RB0 == 0) return '4';
if (PORTBbits.RB1 == 0) return '5';
if (PORTBbits.RB2 == 0) return '6';
if (PORTBbits.RB3 == 0) return 'B';
PORTC = 0x0B; // Activate Row 3
if (PORTBbits.RB0 == 0) return '7';
if (PORTBbits.RB1 == 0) return '8';
if (PORTBbits.RB2 == 0) return '9';
if (PORTBbits.RB3 == 0) return 'C';
PORTC = 0x07; // Activate Row 4
if (PORTBbits.RB0 == 0) return '*';
if (PORTBbits.RB1 == 0) return '0';
if (PORTBbits.RB2 == 0) return '#';
if (PORTBbits.RB3 == 0) return 'D';
return '\0'; // No key pressed
}
void main() {
TRISC = 0xF0; // Set upper nibble of PORTC as input (columns)
TRISB = 0x0F; // Set lower nibble of PORTB as input (rows)
while(1) {
char key = Read_Keypad(); // Read the key pressed
if (key != '\0') {
// Do something with the key
}
}
}
Interfacing a Relay
A relay is used to control high-power devices with the low-power output of the microcontroller. A transistor is often used as a switch to drive the relay coil.
Example: Controlling a Relay using RB0
#include <xc.h>
void main() {
TRISBbits.TRISB0 = 0; // Set RB0 as output (connected to relay)
while(1) {
LATBbits.LATB0 = 1; // Turn ON Relay
__delay_ms(1000); // 1-second delay
LATBbits.LATB0 = 0; // Turn OFF Relay
__delay_ms(1000); // 1-second delay
}
}
Interfacing a Buzzer
A buzzer is an output device that produces sound. It can be driven directly from a microcontroller pin.
Example: Toggling a Buzzer on RB0
#include <xc.h>
void main() {
TRISBbits.TRISB0 = 0; // Set RB0 as output (connected to buzzer)
while(1) {
LATBbits.LATB0 = 1; // Turn ON Buzzer
__delay_ms(500); // 500ms delay
LATBbits.LATB0 = 0; // Turn OFF Buzzer
__delay_ms(500); // 500ms delay
}
}