PIC16F877 Interrupts: Key Techniques and Tips

by Marwen Maghrebi

PIC16F877 interrupts are essential for managing real-time events in microcontroller applications, enhancing responsiveness and performance.

Feature image for PIC16F877 Interrupt project showcasing microcontroller interrupt functionality

Things used in this project

Software apps and online services:

1- MPLAB

2- Proteus 8

Understanding Interrupts in Embedded Systems: Definition, Importance, and Mechanisms

Interrupts are fundamental to embedded systems, including the PIC16F877, providing a mechanism for the microcontroller to respond to real-time events efficiently. This article explores the concept of PIC16F877 interrupts, their importance, how they work, and their various types.

Why Use Interrupts:

Interrupts are powerful tools built into almost every microcontroller, making code easier to write and modify, especially for tasks requiring precise timing or real-time responses such as motor control or communication signals.

Embedded systems can respond to events in two primary ways: polling and interrupts.

  • In polling, the microcontroller continuously checks the status of an event within the main routine. For example, in a program that blinks an LED every two seconds, the microcontroller repeatedly executes code to determine if the time has elapsed to toggle the LED.

  • In an interrupt-driven system, the main routine runs uninterrupted until a specific event triggers an interrupt. Upon triggering, the microcontroller pauses the main routine, executes an interrupt service routine (ISR) to handle the event, and then resumes the main routine.

Advantages of Using Interrupts:

  • Efficient Code Execution: Instead of counting instructions or adding NOPs for precise timing, PIC16F877 interrupts allow for more efficient code execution. Changes in timing can be made by simply adjusting interrupt timing constants.
  • Real-Time Processing: PIC16F877 interrupts ensure consistent timing, crucial for applications such as motor control or communication signals, preventing issues like jerking in motors or errors in communication routines.
  • Simplified Design: Once familiar with PIC16F877 interrupts, developers often design projects around them, simplifying the overall design and speeding up development.

What is an Interrupt?:

An interrupt is an event that causes the microcontroller to pause the main code execution and execute a specific interrupt service routine (ISR). This article focuses on using an external button interrupt with the PIC16F877 to manage tasks such as toggling LEDs, sending UART messages, and utilizing ADC values.

Interrupt Service Routine (ISR):

In the realm of microcontrollers and software systems, the Interrupt Service Routine (ISR) serves a crucial role. It is referred to as a “device driver” when associated with hardware interrupts, responsible for managing interactions between the PIC16F877 microcontroller and external devices. On the other hand, in the context of software interrupts, such as exceptions, signals, or traps, the ISR is alternatively termed an “exception handler,” “signal handler,” or “trap handler.” These designations reflect the ISR’s dual function: facilitating communication between software and hardware components and managing unexpected events or conditions within the software environment.

Using Interrupt with Microcontrollers:

Types of Interrupts in Microcontrollers:

  • Software Interrupts:
    Programmers can intentionally trigger interrupts using specific instructions (e.g., SWI). These software-generated interrupts are useful for various debugging and control scenarios.
  • Hardware Interrupts:
    Peripherals and modules within a microcontroller can generate interrupts to signal events like the completion of a task or an error. Hardware interrupts can be further categorized into internal and external interrupts, based on their source.
  • Vectored vs. Non-Vectored Interrupts:
    • Vectored Interrupts: These have predefined addresses for their ISR handlers in the interrupt vector table. When an interrupt occurs, the microcontroller quickly locates and executes the appropriate ISR.
    • Non-Vectored Interrupts: These use a single common interrupt vector address. The CPU saves the current execution state, jumps to this address, and executes a hard-coded ISR.

Managing Interrupts: Behind the Scenes

Within the PIC16F877A microcontroller, interrupts operate in a unique manner. Unlike vectored systems, where interrupts have specific addresses, here they share a common vector at address 0004h. To navigate this setup, a hardware-implemented program stack comes into play.

This stack operates on a Last-In-First-Out basis, holding addresses of pending instructions. When an interrupt is triggered, the CPU first stores the address of the next instruction in this stack. It then seamlessly transitions to the Interrupt Service Routine (ISR) handler code. After the ISR completes its task, the CPU effortlessly returns to the main program, utilizing the RETFIE instruction and popping the relevant address from the stack.

Understanding Interrupt Logic

The heart of interrupt functionality lies in the intricate interrupt circuitry. This digital logic circuit serves a dual purpose: configuring and managing interrupts within the microcontroller.

In this architecture, all interrupt sources converge onto a single interruption signal to the CPU. This unified approach simplifies the hardware but requires meticulous handling. Interrupt flag bits play a pivotal role here. They are raised when specific interrupt events occur, providing crucial cues to the CPU about the source of interruption.

Insights into Interrupt Flag Bits

Interrupt flag bits serve as silent messengers, quietly indicating when interrupt events take place. To harness their power, programmers must diligently monitor these bits. When an interrupt is flagged, it’s time to swing into action, executing the relevant ISR handler code and promptly clearing the flag bit to ready the system for subsequent interruptions.

Crafting ISR Handlers

ISR handlers are the gatekeepers of interrupt-driven operations. Nestled within specific ROM segments, these handlers are adept at deciphering interrupt sources, executing designated tasks, and tidying up afterward by clearing flag bits.

In the intricate dance of interrupts, mastery over ISR handling is paramount. It empowers developers to create responsive, efficient systems that seamlessly juggle various tasks, ensuring smooth operation even in the face of constant interruptions.

Project Name: Interrupt Handling with PIC Microcontroller

The code configures a PIC microcontroller to handle interrupts triggered by an external button press. When the button is pressed, the interrupt routine is executed, toggling LEDs and sending UART messages accordingly.

Code (in C using XC8 Compiler):

Header and Configuration

This section includes the necessary header files, configuration bits, and definitions. It sets the oscillator frequency and declares function prototypes for the main functionalities of the program

/*
* File: main.c
* Author: Marwen Maghrebi
* 
* Description: 
* This code demonstrates interrupt-driven ADC reading and UART communication on a PIC microcontroller.
* It blinks LEDs, reads analog voltage from a potentiometer, and sends the voltage readings via UART.
* Additionally, it handles an external button interrupt to trigger an LED blink and UART message.
*/

#include <xc.h>
#include <stdint.h>
#include <stdio.h>

// Configuration bits
#pragma config FOSC = HS        // High-Speed Oscillator
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable bit (PWRT enabled)
#pragma config BOREN = ON       // Brown-out Reset Enable bit (BOR enabled)
#pragma config LVP = OFF        // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

#define _XTAL_FREQ 20000000  // Define oscillator frequency for delay

// Function Prototypes
void init_config(void);
void UART_send_string(const char* str);
void __interrupt() ISR(void);

Initialization and Interrupt Service Routine

This part contains the init_config function, which configures the ports, ADC, UART, and interrupts, and the UART_send_string function for sending strings over UART. It also includes the interrupt service routine (ISR) that handles the external interrupt, turns on an LED, and sends a message via UART.

void init_config(void) {
    // Port configurations
    TRISD = 0x00;  // Set PORTD as output for LEDs
    TRISA = 0x01;  // Set RA0 as input for ADC
    TRISB = 0x01;  // Set RB0 as input for button interrupt

    // ADC configuration
    ADCON0 = 0x41;  // ADC ON, Channel 0 (RA0/AN0), Fosc/8 as conversion clock
    ADCON1 = 0x8E;  // Right justify result, set Vref+ to Vdd and Vref- to Vss, AN0 as analog, rest as digital

    // UART configuration
    TXSTAbits.SYNC = 0;  // Asynchronous mode
    TXSTAbits.BRGH = 1;  // High speed
    SPBRG = 129;  // Baud rate 9600 for 20 MHz
    RCSTAbits.SPEN = 1;  // Enable serial port
    TXSTAbits.TXEN = 1;  // Enable transmission

    // Interrupt configuration
    INTCONbits.GIE = 1;  // Enable global interrupts
    INTCONbits.PEIE = 1;  // Enable peripheral interrupts
    INTCONbits.INTE = 1;  // Enable RB0/INT external interrupt
    OPTION_REGbits.INTEDG = 1;  // Interrupt on rising edge

    // Initialize LEDs state
    PORTD = 0x00;
}

void UART_send_string(const char* str) {
    while (*str) {
        while (!TXSTAbits.TRMT);  // Wait until transmit buffer is empty
        TXREG = *str++;  // Transmit character
    }
}

void __interrupt() ISR(void) {
    if (INTCONbits.INTF) {
        // External interrupt occurred
        PORTD = 0x00;  // Turn off the four LEDs
        PORTDbits.RD4 = 1;  // Turn on interrupt-specific LED (RD4)
        __delay_ms(500);
        PORTDbits.RD4 = 0;  // Turn off interrupt-specific LED (RD4)

        // Send UART message indicating interrupt execution
        UART_send_string("Interrupt executed\r\n");

        // Clear the interrupt flag
        INTCONbits.INTF = 0;
    }
}

Main Function

In this section, the main function initializes the configuration, blinks the LEDs, reads the ADC value from a potentiometer, and sends the voltage readings via UART. It continuously performs these tasks in a loop.

void main(void) {
    init_config();

    uint16_t adc_value = 0;
    float voltage = 0.0;
    char buffer[32];

    while (1) {
        // Blink four LEDs every two seconds
        PORTD = 0x0F;  // Turn on LEDs RD0, RD1, RD2, RD3
        __delay_ms(2000);
        PORTD = 0x00;  // Turn off LEDs
        __delay_ms(2000);

        // Read ADC value
        ADCON0bits.GO_DONE = 1;  // Start ADC conversion
        while (ADCON0bits.GO_DONE);  // Wait for conversion to finish
        adc_value = ((uint16_t)(ADRESH << 8)) + ADRESL;  // Cast to uint16_t to avoid warning

        // Convert ADC value to voltage
        voltage = (adc_value * 5.0) / 1023.0;  // Assuming Vref = 5V and 10-bit ADC resolution

        // Send voltage value via UART
        sprintf(buffer, "Voltage: %.2f V\r\n", voltage);
        UART_send_string(buffer);
    }
}

Proteus Configuration :

  • Open Proteus & Create New Project and click next

  • Click on Pick Device
  • Search for PIC16F877A & LEDs & BUTTON

Click on Terminal Mode then choose (DEFAULT & POWER &GROUND)

  • finally make the circuit below and start the simulation
Proteus circuit diagram for PIC16F877 interrupt implementation

That’s all!

If you have any questions or suggestions don’t hesitate to leave a comment below

You Might Also Like

Leave a Comment


Are you sure want to unlock this post?
Unlock left : 0
Are you sure want to cancel subscription?
-
00:00
00:00
Update Required Flash plugin
-
00:00
00:00