Posted on Leave a comment

How to use UART and Interrupts of PIC16F877A

The PIC16F877A has a single ISR function which is called every time some things generate an interrupt. Inside the ISR you have to check for individual flag bits to know which peripheral has generated that interrupt.

/* 
 * File:   main.c
 * Author: abhay
 *
 * Created on July 15, 2023, 11:48 PM
 */

// PIC16F877A Configuration Bit Settings

// 'C' source line config statements

// CONFIG
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable bit (BOR disabled)
#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; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
#define _XTAL_FREQ 16000000
#include <xc.h>
#include <pic16f877a.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include<string.h>




void GPIO_init(void);
void UART_init(void) ;
void tx(unsigned char a);
unsigned char rx();
void txstr(unsigned char *s);

uint8_t RX_chr;
void clear_flag(void);
unsigned char msgACK[16] = "Received";

void __interrupt() myISR() {
    if (RCIF == 1) {
        if (RCSTAbits.OERR) {
            CREN = 0;
            NOP();
            CREN = 1;
        }
        RX_chr = RCREG;
 
        RCIF = 0;
    }

}

/*
 * 
 */
int main(int argc, char** argv) {
    GPIO_init();
    UART_init();
    

    /*
     * Interrupt Setup START
     */
    INTCONbits.GIE = 1;
    INTCONbits.PEIE = 1;
    PIE1bits.TXIE = 0;
    PIE1bits.RCIE = 1;
    /*
     * Interrupt Setup END
     */


    while (1) {

        if (RX_chr == '0') {
        tx(RX_chr);
        RD1 = 0 ; // LED OFF
        txstr(msgACK);
        clear_flag();
    }

    if (RX_chr == 'a') {
        RD1 = 1; // LED ON
        txstr(msgACK);
        clear_flag();
    }

    }

    return (EXIT_SUCCESS);
}


void clear_flag(void) {
    RX_chr = 0;
}

void GPIO_init(void) {

    TRISD &= ~(1 << 1);
    RD1 = 0;
    
    /*
     * UART Pins Initialize
     */
    TRISCbits.TRISC6 = 0; // TX
    TRISCbits.TRISC7 = 1; //RX


}
void UART_init() {
        TXSTAbits.CSRC = 0;
        TXSTAbits.TX9 = 0;
        TXSTAbits.TXEN = 1;
        TXSTAbits.SYNC = 0;
        /* BRGH - High Baud Rate Select Bit
         * 1: High Speed
         * 0: Low Speed
         */
        TXSTAbits.BRGH = 1;
        TXSTAbits.TRMT = 0;
        TXSTAbits.TX9D = 0;

        RCSTAbits.SPEN = 1;
        RCSTAbits.RX9 = 0;
        RCSTAbits.SREN = 0;
        RCSTAbits.CREN = 1;
        RCSTAbits.ADDEN = 0;
        RCSTAbits.FERR = 0;
        RCSTAbits.OERR = 0;
        RCSTAbits.RX9D = 0;

        /*
         * Baud Rate Formula
         *  Asynchronous 
         *      Baud Rate = Fosc / (64 (x + 1))
         *          Baud Rate x (64 (x+1)) = FOSC
         *        SPBRG=  (x) = ( Fosc/( Baud_Rate * 64 ) ) - 1
         */

        SPBRG = 103; // Baud Rate 9600
        TXIF = RCIF = 0;
    }

void tx(unsigned char a) {
        
        while (TXIF == 0); // Wait till the transmitter register becomes empty
        TXIF = 0; // Clear transmitter flag
        TXREG = a; // load the char to be transmitted into transmit reg
    }

    unsigned char rx() {
        while (!RCIF);
        RCIF = 0;
        return RCREG;
    }

 void txstr(unsigned char *s) {
        while (*s) {
            tx(*s++);

            __delay_us(10);
        }

    }
Posted on Leave a comment

Interface LCD 16×2 Character in 4-Bit Mode with PIC16F877A

To save the number of pins of the microcontroller we interface the character LCD in 4-bit mode.

If we take a look at the PINS of the character LCD

[ VSS | VDD | V0 | R/S | R/W | E | D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 | A | K ]

If we were to use this LCD in 8-bit mode we have to use 8 GPIO pins for the DATA (D0-D7) and three GPIO Pins for control(R/S, R/W, E).

But in 4-bit mode, we use three control pins and only four data pins D4-D7 .

Schematics

Schematics diagram for Home Automation using Bluetooth

In the above schematic, I have interfaced the LCD in 4-bit mode.

LCD BACKLIGHT
I have connected a 220 ohm resistor to the K pin; since my lcd module has a 100 ohm resistor on board. It is labeled R8. So 100 + 220 = 320 ohm resistance total.
If the LED takes approx. 3V forward volage then the current will be given by equation
Current = (VCC – Forward Voltage) / Total resistance
=> (5v-3v)/320ohm = 2v / 320 ohm = 0.00625 A = 6.25 mA(approx.)

LCD Command

You can make your own commands using this table shown below.

CODE

main.hLCD_16x2.hboard.h
/* 
 * File:   main.c
 * Author: abhay
 *
 * Created on July 14, 2023, 12:05 AM
 */

// PIC16F877A Configuration Bit Settings

// 'C' source line config statements

// CONFIG
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable bit (BOR disabled)
#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; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
#define _XTAL_FREQ 16000000
#include <xc.h>
#include <pic16f877a.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "board.h"
#include "LCD_16x2.h"
#include<string.h>

char GPIO_init(void);

/*
 * 
 */
int main(int argc, char** argv) {
    GPIO_init();
    InitLCD(); // Initialize LCD in 8bit mode

    //const char *msg = "Hello World!";
    // const char *msg1 = "Abhay";
    //WriteStringToLCD(msg1);
    char lcd_buff1[16] = "Abhay Kant";
    char lcd_buff2[16] = "Kant";
    
    
    lcd_set_cursor(1, 1);
    WriteDataToLCD('E');
    WriteDataToLCD('X');
    WriteDataToLCD('A');
    WriteDataToLCD('S');
    WriteDataToLCD('U');
    WriteDataToLCD('B');
    WriteDataToLCD('.');
    WriteDataToLCD('C');
    WriteDataToLCD('O');
    WriteDataToLCD('M');

    lcd_set_cursor(2, 1);
    WriteStringToLCD(lcd_buff1);

    while (1) {

    }

    return (EXIT_SUCCESS);
}

char GPIO_init(void) {
    /*
     * TRIS = Data Direction Register
     * 0 = OUTPUT
     * 1 = INPUT
     * TRISD &= ~(1 << 1); // LED RD1 as OUTPUT
     * TRISD1 = 0; // RD1 as OUTPUT
     */



    LED_PORT_DIR &= ~(1 << 1);
    LED_PIN = 0;

    Relay_1a_DIR &= ~(1 << 0);
    Relay_1a_PIN = 0;

    Relay_1b_DIR &= ~(1 << 2);
    Relay_1b_PIN = 0;

    Relay_2a_DIR &= ~(1 << 4);
    Relay_2a_PIN = 0;

    Relay_2b_DIR &= ~(1 << 5);
    Relay_2b_PIN = 0;

    /*
     * LCD Pins Initialize
     */
    TRISB = 0;
    return 0;
}
/*
 * File:   LCD_16x2.h
 * Author: abhay
 *
 * Created on July 16, 2023, 6:21 PM
 */

#ifndef LCD_16X2_H
#define	LCD_16X2_H

#include "board.h"


#ifdef	__cplusplus
extern "C" {
#endif
    /*
     *Sr.No.	Hex Code	Command to LCD instruction Register
        1       01          Clear display screen
        2       02          Return home
        3       04          Decrement cursor (shift cursor to left)
        4       06          Increment cursor (shift cursor to right)
        5       05          Shift display right
        6       07          Shift display left
        7       08          Display off, cursor off
        8       0A          Display off, cursor on
        9       0C          Display on, cursor off
        10  	0E          Display on, cursor blinking off
        11  	0F          Display on, cursor blinking on
        12      10          Shift cursor position to left
        13      14          Shift the cursor position to the right
        14      18          Shift the entire display to the left
        15      1C          Shift the entire display to the right
        16      80          Force cursor to the beginning ( 1st line)
        17  	C0          Force cursor to the beginning ( 2nd line)
        18  	38          2 lines and 5×7 matrix
     */
    // Define Pins

#define LCD_RS         RB1     // RS pin for LCD
#define LCD_RW         RB2     // RS pin for LCD
#define LCD_E          RB3     // Enable pin for LCD
#define LCD_Data_Bus_D4    RB4    // Data bus bit 4
#define LCD_Data_Bus_D5    RB5    // Data bus bit 5
#define LCD_Data_Bus_D6    RB6    // Data bus bit 6
#define LCD_Data_Bus_D7    RB7    // Data bus bit 7
    // Define Pins direction registrers
#define LCD_E_Dir           TRISB3
#define LCD_RS_Dir          TRISB1
#define LCD_RW_Dir          TRISB2
#define LCD_Data_Bus_Dir_D4     TRISB4
#define LCD_Data_Bus_Dir_D5     TRISB5
#define LCD_Data_Bus_Dir_D6    TRISB6
#define LCD_Data_Bus_Dir_D7   TRISB7
    // Constants
#define E_Delay       1000
    // Function Declarations
    void WriteCommandToLCD(unsigned char);
    void WriteDataToLCD(char);
    void InitLCD(void);
    void WriteStringToLCD(const char*);
    void ClearLCDScreen(void);

    void ToggleEpinOfLCD(void) {
        LCD_E = 1; // Give a pulse on E pin
        __delay_us(E_Delay); // so that LCD can latch the
        LCD_E = 0; // data from data bus
        __delay_us(E_Delay);
    }

    void WriteCommandToLCD(unsigned char Command) {
        LCD_RS = 0; // It is a command
        PORTB &= 0x0F; // Make Data pins zero
        PORTB |= (Command & 0xF0); // Write Upper nibble of data
        ToggleEpinOfLCD(); // Give pulse on E pin
        PORTB &= 0x0F; // Make Data pins zero
        PORTB |= ((Command << 4)&0xF0); // Write Lower nibble of data
        ToggleEpinOfLCD(); // Give pulse on E pin
    }

    void WriteDataToLCD(char LCDChar) {
        LCD_RS = 1; // It is data
        PORTB &= 0x0F; // Make Data pins zero
        PORTB |= (LCDChar & 0xF0); // Write Upper nibble of data
        ToggleEpinOfLCD(); // Give pulse on E pin
        PORTB &= 0x0F; // Make Data pins zero
        PORTB |= ((LCDChar << 4)&0xF0); // Write Lower nibble of data
        ToggleEpinOfLCD(); // Give pulse on E pin
    }

    void InitLCD(void) {
        // Firstly make all pins output
        LCD_E = 0; // E  = 0
        LCD_RS = 0; // RS = 0
        LCD_Data_Bus_D4 = 0; // Data bus = 0
        LCD_Data_Bus_D5 = 0; // Data bus = 0
        LCD_Data_Bus_D6 = 0; // Data bus = 0
        LCD_Data_Bus_D7 = 0; // Data bus = 0
        LCD_E_Dir = 0; // Make Output
        LCD_RS_Dir = 0; // Make Output
        LCD_RW_Dir = 0;
        LCD_RW = 0;
        LCD_Data_Bus_Dir_D4 = 0; // Make Output
        LCD_Data_Bus_Dir_D5 = 0; // Make Output
        LCD_Data_Bus_Dir_D6 = 0; // Make Output
        LCD_Data_Bus_Dir_D7 = 0; // Make Output
        ///////////////// Reset process from datasheet //////////////
        __delay_ms(40);
        PORTB &= 0x0F; // Make Data pins zero
        PORTB |= 0x30; // Write 0x3 value on data bus
        ToggleEpinOfLCD(); // Give pulse on E pin
        __delay_ms(6);
        PORTB &= 0x0F; // Make Data pins zero
        PORTB |= 0x30; // Write 0x3 value on data bus
        ToggleEpinOfLCD(); // Give pulse on E pin
        __delay_us(300);
        PORTB &= 0x0F; // Make Data pins zero
        PORTB |= 0x30; // Write 0x3 value on data bus
        ToggleEpinOfLCD(); // Give pulse on E pin
        __delay_ms(2);
        PORTB &= 0x0F; // Make Data pins zero
        PORTB |= 0x20; // Write 0x2 value on data bus
        ToggleEpinOfLCD(); // Give pulse on E pin
        __delay_ms(2);
        /////////////// Reset Process End ////////////////
        WriteCommandToLCD(0x28); //function set
        WriteCommandToLCD(0x0c); //display on,cursor off,blink off
        WriteCommandToLCD(0x01); //clear display
        WriteCommandToLCD(0x06); //entry mode, set increment
        WriteCommandToLCD(0x0e); //display on,cursor on,blink off
        WriteCommandToLCD(0x0f); //display on,cursor on,blink on
    }

    void WriteStringToLCD(const char *s) {
        while (*s) {
            WriteDataToLCD(*s++); // print first character on LCD
        }
    }

    void ClearLCDScreen(void) // Clear the Screen and return cursor to zero position
    {
        WriteCommandToLCD(0x01); // Clear the screen
        __delay_ms(2); // Delay for cursor to return at zero position
    }

    void lcd_set_cursor(uint8_t row, uint8_t col) {
        if (row == 1) {
            WriteCommandToLCD(0x80 | (col - 1));
        } else if (row == 2) {
            WriteCommandToLCD(0xC0 | (col - 1));
        }
    }

#ifdef	__cplusplus
}
#endif

#endif	/* LCD_16X2_H */

/* 
 * File:   board.h
 * Author: abhay
 *
 * Created on July 14, 2023, 12:41 AM
 */

#ifndef BOARD_H
#define	BOARD_H

#ifdef	__cplusplus
extern "C" {
#endif
/**
 * LED 
 * color: RED
 * @param PORT: D
 * @param PIN: 1
 */
#define LED_PORT_DIR TRISD
#define LED_Port PORTD
#define LED_PIN PORTDbits.RD1
    
#define Relay_1a_DIR TRISD
#define Relay_1a_Port PORTD
#define Relay_1a_PIN PORTDbits.RD0

#define Relay_1b_DIR TRISD    
#define Relay_1b_Port PORTD
#define Relay_1b_PIN PORTDbits.RD2

#define Relay_2a_DIR TRISD
#define Relay_2a_Port PORTD
#define Relay_2a_PIN PORTDbits.RD4

#define Relay_2b_DIR TRISD
#define Relay_2b_Port PORTD
#define Relay_2b_PIN PORTDbits.RD5

#ifdef	__cplusplus
}
#endif

#endif	/* BOARD_H */

Posted on Leave a comment

How to Control the GPIO of PIC16F877A using MPLAB X IDE

The PIC16F877A microcontroller is a popular choice for embedded systems development due to its versatility and ease of use. One of the essential aspects of working with microcontrollers is controlling General Purpose Input/Output (GPIO) pins. In this blog post, we will explore how to control the GPIO pins of the PIC16F877A using MPLAB X IDE, a powerful Integrated Development Environment.

Prerequisites:
To follow along with this tutorial, you will need the following:

  1. PIC16F877A microcontroller.
  2. MPLAB X IDE installed on your computer.
  3. MPLAB XC8 Compiler.

Step 1: Create a New Project
Launch MPLAB X IDE and create a new project by navigating to File -> New Project. Select “Microchip Embedded” under “Categories” and “Standalone Project” under “Projects”. Choose the PIC16F877A as the device and specify a name and location for your project. Click “Finish” to create the project.

Step 2: Configure the GPIO Pins
To control the GPIO pins, we need to configure them as inputs or outputs. In the project window, open the “main.c” source file. Locate the main function and add the necessary code to configure the GPIO pins.

To set a pin as an output, use the TRISx register, where x represents the port name (A, B, C, etc.). For example, to set RB0 as an output pin, use the following code:

TRISBbits.TRISB0 = 0; // Set RB0 as an output pin

To set a pin as an input, use the same TRISx register and set the corresponding bit to 1. For example, to set RA2 as an input pin, use the following code:

TRISAbits.TRISA2 = 1; // Set RA2 as an input pin

Step 3: Control the GPIO Pins Once the GPIO pins are configured, we can control their state by manipulating the corresponding PORT registers. To set an output pin high (logic level 1), write 1 to the corresponding bit in the PORT register. For example, to set RB0 high, use the following code:

PORTBbits.RB0 = 1; // Set RB0 high

To set an output pin low (logic level 0), write 0 to the corresponding bit in the PORT register. For example, to set RB0 low, use the following code:

PORTBbits.RB0 = 0; // Set RB0 low

To read the state of an input pin, you can directly access the corresponding PORT register. For example, to read the state of RD2, use the following code:

if (PORTAbits.RD2 == 1) {
    // RD2 is high
} else {
    // RD2 is low
}

PORTA: It is configured as an analog port by default.
When you want to use it as a Digital I/O port, you have to configure the ADCON1 register.

Demo Code

/* 
 * File:   main.c
 * Author: abhay
 *
 * Created on July 14, 2023, 12:05 AM
 */

// PIC16F877A Configuration Bit Settings

// 'C' source line config statements

// CONFIG
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable bit (BOR disabled)
#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; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
#define _XTAL_FREQ 16000000
#include <xc.h>
#include <pic16f877a.h>
#include <stdio.h>
#include <stdlib.h>
#include "board.h"

/*
 * 
 */
int main(int argc, char** argv) {
    /*
     * TRIS = Data Direction Register
     * 0 = OUTPUT
     * 1 = INPUT

     */
// Make the Pin 1 of PORT D as output 
      TRISD &= ~(1 << 1); // LED RD1 as OUTPUT
      TRISD1 = 0; // RD1 as OUTPUT
     
    
    // Make the Pin 0 of PORT A as digital input
    ADCON1bits.PCFG0 = 0;
    ADCON1bits.PCFG1 = 1;
    ADCON1bits.PCFG2 = 1;
    ADCON1bits.PCFG3 = 0;
    TRISA0 = 1; // button 
    
    while (1) {
        if((RA0 ) == 1)
        {
            RD1 = 1;
        }
        else {
            PORTD &= ~(1<<1);
        }
       
    }

    return (EXIT_SUCCESS);
}

Step 4: Build and Program the Microcontroller
Now that we have written the code, it’s time to build and program the microcontroller. Connect your PIC16F877A microcontroller to your computer via a suitable programmer/debugger. Ensure that the proper hardware connections are made.

Step 5: Test the GPIO Control
Once the programming is complete, disconnect the programming cable and power the microcontroller using an appropriate power supply. Connect LEDs, switches, or other devices to the configured GPIO pins. Execute the code on the microcontroller and observe the desired behavior of the GPIO pins based on the control logic implemented in your code.

Conclusion:
Controlling the GPIO pins of the PIC16F877A microcontroller using MPLAB X IDE is a fundamental skill in embedded systems development. By following this step-by-step guide, you have learned how to configure the GPIO pins as inputs or outputs and manipulate their states using the appropriate registers. With this knowledge, you can now start building a wide range of projects that involve interacting with the external world through GPIO pins.

Remember to refer to the PIC16F877A datasheet for detailed information on register names, bit assignments, and other specific details related to the microcontroller.

Happy coding and exploring the world of embedded systems!