© All rights reserved. Powered by Florisera.

RSS Daily tech news
  • E-scooter crashes mainly caused by reckless driving
    Crashes on electric scooters are mostly due to the behavior of the riders, with one-handed steering and riding in a group being some of the largest risk factors. The researchers are also concerned about riders who deliberately crash or cause dangerous situations when riding, a phenomenon that seems to be specific to electric scooters.
  • These electronics-free robots can walk right off the 3D-printer
    This a robot can walk, without electronics, and only with the addition of a cartridge of compressed gas, right off the 3D-printer. It can also be printed in one go, from one material.
  • Good vibrations: Scientists discover a groundbreaking method for exciting phonon-polaritons
    Newly published research demonstrates a novel way of generating phonon-polaritons by making electrons collide with crystal lattice structures. The discovery paves the way for cheaper, smaller long-wave infrared light sources and more efficient device cooling.
  • New platform lets anyone rapidly prototype large, sturdy interactive structures
    A rapid prototyping platform called VIK (Voxel Invention Kit) enables makers without engineering expertise to create large-scale interactive devices using a series of reconfigurable electromechanical building blocks. These user-friendly components can be assembled using only a soldering iron and a pair of pliers.
  • Lords of the molecular rings: An innovative shortcut to high-performance organic materials
    Scientists have unveiled an innovative approach for synthesizing azaparacyclophanes (APCs), a class of highly advanced ring-shaped molecular structures with immense potential in material science. Their innovative Catalyst-Transfer Macrocyclization (CTM) method streamlines the production of these complex macrocycles, paving the way for more efficient and scalable applications in organic electronics, optoelectronics, and supramolecular chemistry -- such […]
  • Scientists tune in to rhombohedral graphene's potential
    Scientists are investigating how structures made from several layers of graphene stack up in terms of their fundamental physics and their potential as reconfigurable semiconductors for advanced electronics.

How to use interrupts in microcontrollers

by Florius
Featured image of how to use interrupts in the PIC16F877A

In this tutorial we will learn how to use external interrupts in PIC microcontrollers. We will go in depth on how to set it up in hardware, and how to configure it correctly within the program. For the examples we will use microcontrollers from the PIC16F family; in particular the PIC16F877A.

1. What are interrupts and when do you use them?

An external interrupt is an event that can cause the microcontroller to stop what it is doing and execute a special routine called an interrupt service routine (ISR). The interrupt is typically caused by a change in the state of an external device, such as a button press or a sensor reading. This allows for real-time responses, as the microcontroller can react to the event immediately.

When an interrupt occurs, the ISR is called, and any code that is inside the ISR will be run. Once the ISR is finished, the microcontroller will return to the main program, at the point where it left off. There are several types of interrupts in the PIC16F877A, such as:

  • External interrupts,
  • Timer 0 (and timer 1) overflow interrupt,
  • RB port change interrupt,
  • Serial port transmit/receive interrupts,
  • ADC/comparator interrupts,
  • Watchdog timer interrupt.

To make sure that interrupts dont happen in the wrong order, there is a priority listed to them; the external interrupt has the highest priority of them all, and that is the one we will focus on in this tutorial.

2. Configuring the interrupt settings

Most of the interrupts are configured in the INTCON register, which contains variable flag bits for the TMR0 register overflow, RB port change and external RB0/INT pin interrupts.

The following bits in the INTCON register are relevant to external interrupts:

  • GIE: Global Interrupt Enable bit. This bit enables or disables all interrupts.
  • PEIE: Peripheral Interrupt Enable bit. This bit enables or disables the peripheral interrupts (Timer 0, Timer 1, Serial Port, A/D Converter, and Comparator).
  • INTE: RB0/INT External Interrupt Enable bit. This bit enables or disables the external interrupt.

To set the interrupt settings, you write to the INTCON register. The following table shows the values you need to write to the INTCON register to enable or disable the different interrupts:

BitValueDescription
GIE1Enables all interrupts.
PEIE1Enables the peripheral interrupts.
INTE1Enables the external interrupt.

Once RB0 pin is declared an external interrupt pin, the external interrupt flag, INTF, changes to 1 anytime it becomes low. Consequently, the code inside the void interrupt function will execute since the Interrupt Service Routine (ISR) will be called. Proper knowledge of these concepts is essential for external interrupts function.

2.1 Interrupt Service Routine (ISR)

An ISR, or Interrupt Service Routine, is a special routine that is called when an interrupt occurs. The ISR typically performs the desired action, such as turning on an LED or reading a sensor value.

The ISR is located in the program memory. The following is an example of an ISR for the external interrupt:

				
					void interrupt ISR() { 
        // Perform the desired action. 
        INTCONbits.INTF = 0; // Clear the interrupt flag
    }
				
			

The ISR is called when the interrupt occurs. The microcontroller will automatically save the state of the registers before executing the ISR. Once the ISR is finished, the microcontroller will restore the state of the registers and return to the main program. It is of vital importance, that you clear the interrupt flag after doing the task, because if another interrupt appears, it will not interrupt the current interrupt, else it might go wrong.

Here are some of the things to keep in mind when writing an ISR:

  • The ISR should be as short as possible.
  • The ISR should not make any changes to the global variables.
  • The ISR should not call any functions that make changes to the global variables.
  • The ISR should not call any functions that can cause an interrupt.

2.2 Program code

For this example we will create a program that shows a counter that increments every time the main program is being interrupted by the external interrupt. In this particular example, it is not too different from having a button to increase the counter, however, you must understand that there is a fundamental difference between the two. With the external interrupt, it literally interrupts the program to do the ISR. We will reuse the program code, from one of our previous tutorials on interfacing with an LCD, to display the data.

An important notice; in the original code for LCDs, we used PORTB as 8-bit databus to interface with the LCD. However, we moved all this to PORTC, as RB0 is now the interrupt. In my program code, this was done by just writing “#define Lcd_Port PORTC”. Of course, it can also be changed to 4-bit mode, and only use RB4-RB7 as databus. If you are using any other microcontroller, please consult my page on interfacing with the LCD to learn in just a few steps how to set it up for your particular case.

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

#define _XTAL_FREQ 20000000

// Configuration bits settings
#pragma config FOSC = HS        // External Oscillator (HS)
#pragma config WDTE = OFF       // Watchdog Timer Disabled
#pragma config PWRTE = ON       // Power-Up Timer Enabled
#pragma config BOREN = OFF      // Brown-out Reset Disabled
#pragma config LVP = OFF        // Low-Voltage Programming Disabled

// Pin definitions
#define Lcd_EN RD7
#define Lcd_RW RD6
#define Lcd_RS RD5

// Port definitions
#define Lcd_Port PORTC // Use PORTC for LCD data lines

// Function prototypes - See further down
void LCD_DataWrite(char dat);
void LCD_CommandWrite(char cmd);
void LCD_Initialize();
void LCD_String(const char* text);
void LCD_Clear();

volatile uint16_t counter = 0; // Declare a volatile counter variable

void __interrupt() ISR() {
    if (INTF) {
        counter++; // Increment the counter
        LCD_Clear(); // Clear the LCD screen
        char buffer[17]; // Buffer to hold the counter as a string
        sprintf(buffer, "Counter: %u", counter); // Format the counter value
        LCD_String(buffer); // Display the counter on the LCD
        INTF = 0; // Clear the RB0 external interrupt flag
    }
}

void main() {
    TRISB0 = 1; // Set RB0 as input for the external interrupt
    TRISC = 0x00; // Set PORTC as output for LCD data lines
    TRISD = 0x00; // Set PORTD as output
    PORTD = 0x00; // Clear PORTD initially

    INTCON = 0b11010000; // Enable Global Interrupt, Peripheral Interrupt, and RB0 External Interrupt, set flag to 0

    LCD_Initialize();

    while (1) {
        // Your main program logic here
    }
}

// Other functions remain the same as in the original code on:
// https://florisera.com/embedded-systems/connecting-pic16f877a-with-an-lcd-screen/

// Function to send data byte to the LCD (HD44780)
void LCD_DataWrite(char dat)
{
   Lcd_Port=dat;	  // Set the data on the PORT
   Lcd_RS=1;	      // Select the Register for sending data by pulling RS HIGH
   Lcd_RW=0;          // Select Write by pulling RW LOW
   Lcd_EN=1;	      // Send a High-to-Low Pulse at EN pin
   __delay_us(10);
   Lcd_EN=0;          // set EN pin back to LOW
   __delay_ms(2);     // Add a delay to ensure proper timing, and no need for Buffer check
}

// Function to send command to the LCD (HD44780)
void LCD_CommandWrite(char cmd)
{
   Lcd_Port=cmd;	  // Set the data on the PORT
   Lcd_RS=0;	      // Select the Register for sending a command by pulling RS LOW
   Lcd_RW=0;          // Select Write by pulling RW LOW
   Lcd_EN=1;	      // Send a High-to-Low Pulse at EN pin
   __delay_us(10);
   Lcd_EN=0;          // set EN pin back to LOW
   __delay_ms(2);     // Add a delay to ensure proper timing, and no need for Buffer check
}

// Function to initialize the LCD
void LCD_Initialize()
{
    __delay_ms(15);    // 15 ms start up time, as per datasheet
    LCD_CommandWrite(0x38); // "Function set": 8-bit data, 2-line display, 5x8 font. 0b00111000
    LCD_CommandWrite(0x0C); // "Display on/off": Display on, cursor off, blinking off. 0b00001100
    LCD_CommandWrite(0x01); // "Clear Display":  Clear display. 0b00000001
    LCD_CommandWrite(0x06); // "Entry mode set": cursor increments, no display shift. 0b00000110
}

void LCD_String(const char* text) {
    int charIndex = 0;

    while (text[charIndex] != '\0') { //continue incrementing untill it reaches the null
        LCD_DataWrite(text[charIndex]);

        // Check if the text is longer than 16 characters & does not have a null on the 17th character
        if (charIndex == 15 && text[16] != '\0') {
            LCD_CommandWrite(0xC0); // Set DDRAM address to second line
        }

        charIndex++;
    }
}

void LCD_Clear() {
    LCD_CommandWrite(0x01); // Clear display
    __delay_ms(2);
}
				
			
Florius

Hi, welcome to my website. I am an electronic enthusiast, writing about my previous studies, work & research related topics and other interests. I hope you enjoy reading it and that you learned something new.

More Posts

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Visual Portfolio, Posts & Image Gallery for WordPress