PIC16 Multi-Tasking Tutorial: LED & Timer Projects

Welcome to this tutorial where we will explore essential techniques for working with PIC16 microcontrollersUnderstanding PIC Family Variants: PIC12, PIC16, PIC18, and BeyondUnderstanding PIC Family Variants: PIC12, PIC16, PIC18, and BeyondExplore PIC microcontroller families: learn how PIC12’s compact design, PIC16’s balanced features, and PIC18’s robust performance for innovative projects.-starting with a simple blinking LED project and moving towards basic multi-tasking strategies. This hands-on journey will help you understand how to structure code for multiple tasks, manage timing, and ensure that various system components cooperate smoothly. Whether you are an electronics hobbyist, an engineering student, or an experienced developer aiming to polish your skills, you will find valuable insights in these projects.

Introduction🔗

PIC16 microcontrollersUnderstanding PIC Family Variants: PIC12, PIC16, PIC18, and BeyondUnderstanding PIC Family Variants: PIC12, PIC16, PIC18, and BeyondExplore PIC microcontroller families: learn how PIC12’s compact design, PIC16’s balanced features, and PIC18’s robust performance for innovative projects. are known for their balance of simplicity and power. They are widely used in countless embedded systems applications, from lighting control to industrial automation. In this tutorial, we will progressively build from a basic blinking LED to a system that can handle multiple tasks (or “multi-task”) in a structured way.

You will learn:

Prerequisites🔗

Before we begin, you should have:

1. A PIC16Understanding PIC Family Variants: PIC12, PIC16, PIC18, and BeyondUnderstanding PIC Family Variants: PIC12, PIC16, PIC18, and BeyondExplore PIC microcontroller families: learn how PIC12’s compact design, PIC16’s balanced features, and PIC18’s robust performance for innovative projects.-based microcontroller (e.g., PIC16F877A or PIC16F887).

2. A development board or breadboard setup with necessary components (LEDs, resistors, etc.).

3. MPLAB X IDEGetting Started with MPLAB X and the XC8 CompilerGetting Started with MPLAB X and the XC8 CompilerSet up MPLAB X IDE and XC8 compiler for PIC programming with our comprehensive guide detailing installation, configuration, and debugging techniques. installed and configured.

4. XC8 CompilerGetting Started with MPLAB X and the XC8 CompilerGetting Started with MPLAB X and the XC8 CompilerSet up MPLAB X IDE and XC8 compiler for PIC programming with our comprehensive guide detailing installation, configuration, and debugging techniques. properly set up.

A basic understanding of the C programming language and familiarity with PIC16 pins, configuration bitsUsing Configuration Bits to Customize Your PIC ProjectUsing Configuration Bits to Customize Your PIC ProjectDiscover how to set PIC microcontroller configuration bits. Learn key steps for oscillator, watchdog, and code protection to ensure reliable startup., and I/O operations will also be helpful.

Project 1: Blinking an LED🔗

The classic “Hello World” of microcontrollers is to blink an LED. Let’s break down the steps:

1. Hardware Setup

2. Configuration BitsUsing Configuration Bits to Customize Your PIC ProjectUsing Configuration Bits to Customize Your PIC ProjectDiscover how to set PIC microcontroller configuration bits. Learn key steps for oscillator, watchdog, and code protection to ensure reliable startup.

Make sure to configure the clock source, watchdog timerLow-Power Strategies: Maximizing PIC Battery LifeLow-Power Strategies: Maximizing PIC Battery LifeDiscover proven low-power strategies for PIC microcontrollers that maximize battery life through smart oscillator use, sleep modes, and efficient coding., and other relevant parameters in your MPLAB X project. The exact configuration will depend on your specific PIC16 device, but a minimal example might look like this:

// Example: PIC16F877A configuration (XC8 pseudocode)
#pragma config FOSC = HS     // High-speed oscillator
#pragma config WDTE = OFF    // Watchdog timer disabled
#pragma config PWRTE = ON    // Power-up timer enabled
#pragma config BOREN = ON    // Brown-out reset enabled
#pragma config LVP = OFF     // Low-voltage (ICSP) disabled
#pragma config CPD = OFF     // Data code protection off
#pragma config WRT = OFF     // Flash program memory write protection off
#pragma config CP = OFF      // Flash program memory code protection off

3. Software Delays Method

The simplest blinking approach is to turn the LED on for a certain number of milliseconds, then off for the same duration. This is done using a software delay (__delay_ms() in XC8Getting Started with MPLAB X and the XC8 CompilerGetting Started with MPLAB X and the XC8 CompilerSet up MPLAB X IDE and XC8 compiler for PIC programming with our comprehensive guide detailing installation, configuration, and debugging techniques.).

#define _XTAL_FREQ 20000000  // Define crystal frequency (20 MHz example)
void main(void) {
    TRISCbits.TRISC0 = 0;  // Configure RC0 as output
    while (1) {
        LATCbits.LATC0 = 1; // Turn LED on
        __delay_ms(500);
        LATCbits.LATC0 = 0; // Turn LED off
        __delay_ms(500);
    }
}

Pros of Software Delays:

  • Simple and easy to implement.

Cons of Software Delays:

  • The microcontroller is blocked during the delay (no other tasks can execute).

This limitation leads us to timer-based approaches and eventually toward multi-tasking techniques.

Project 2: Using Timer Interrupts🔗

Relying on software delays can be inefficient, as it blocks the processor in busy-wait loops. Instead, we can use timer interruptsImplementing Interrupt-Driven Systems for Real-Time ApplicationsImplementing Interrupt-Driven Systems for Real-Time ApplicationsLearn to configure and optimize PIC microcontroller interrupts for real-time performance. Enhance responsiveness and efficiency using best practices.:

1. Set Up a Timer: Select a timer (e.g., Timer0), configure its prescaler, and set the period to generate regular interruptsImplementing Interrupt-Driven Systems for Real-Time ApplicationsImplementing Interrupt-Driven Systems for Real-Time ApplicationsLearn to configure and optimize PIC microcontroller interrupts for real-time performance. Enhance responsiveness and efficiency using best practices..

2. Enable InterruptsImplementing Interrupt-Driven Systems for Real-Time ApplicationsImplementing Interrupt-Driven Systems for Real-Time ApplicationsLearn to configure and optimize PIC microcontroller interrupts for real-time performance. Enhance responsiveness and efficiency using best practices.: Enable both the peripheral interrupt and global interruptImplementing Interrupt-Driven Systems for Real-Time ApplicationsImplementing Interrupt-Driven Systems for Real-Time ApplicationsLearn to configure and optimize PIC microcontroller interrupts for real-time performance. Enhance responsiveness and efficiency using best practices. for the chosen timer.

3. Interrupt Service RoutineImplementing Interrupt-Driven Systems for Real-Time ApplicationsImplementing Interrupt-Driven Systems for Real-Time ApplicationsLearn to configure and optimize PIC microcontroller interrupts for real-time performance. Enhance responsiveness and efficiency using best practices. (ISR): In the ISRImplementing Interrupt-Driven Systems for Real-Time ApplicationsImplementing Interrupt-Driven Systems for Real-Time ApplicationsLearn to configure and optimize PIC microcontroller interrupts for real-time performance. Enhance responsiveness and efficiency using best practices., toggle the LED or increment a counter that eventually toggles the LED.

Example: Configuring Timer0 at 1 ms tick:

// Timer0 1ms overflow example for PIC16F877A
#define _XTAL_FREQ 20000000
void __interrupt() ISR(void) {
    static unsigned int tickCount = 0;
    if (T0IF) {
        T0IF = 0;             // Clear timer flag
        TMR0 = 256 - 125;     // Reload Timer0 preload for ~1ms at 20MHz / 4 / prescaler
        tickCount++;
        if(tickCount >= 500) {
            LATCbits.LATC0 = !LATCbits.LATC0;  // Toggle LED every 500 ms
            tickCount = 0;
        }
    }
}
void main(void) {
    // Configure Timer0
    OPTION_REG = 0x07;   // Prescaler 1:256
    TMR0 = 256 - 125;    // Preload for ~1ms interrupt
    INTCONbits.T0IE = 1; // Enable Timer0 interrupt
    INTCONbits.GIE = 1;  // Global interrupt enable
    TRISCbits.TRISC0 = 0; // RC0 as output
    LATCbits.LATC0 = 0;   // LED off initially
    while (1) {
        // Main loop can handle other tasks without being blocked
    }
}

Key Advantages of Timer InterruptsImplementing Interrupt-Driven Systems for Real-Time ApplicationsImplementing Interrupt-Driven Systems for Real-Time ApplicationsLearn to configure and optimize PIC microcontroller interrupts for real-time performance. Enhance responsiveness and efficiency using best practices.:

Introducing Multi-Tasking Concepts🔗

As your application complexity grows (e.g., you have multiple LEDs to blink at different rates, or you must handle sensorAnalog-to-Digital Conversion: Connecting Sensors to PICAnalog-to-Digital Conversion: Connecting Sensors to PICExplore our step-by-step PIC microcontroller ADC tutorial, including sensor interfacing techniques and C code examples to achieve accurate conversions. readings and user inputs), you need a more structured approach-often referred to as multi-tasking.

In microcontrollers like PIC16Understanding PIC Family Variants: PIC12, PIC16, PIC18, and BeyondUnderstanding PIC Family Variants: PIC12, PIC16, PIC18, and BeyondExplore PIC microcontroller families: learn how PIC12’s compact design, PIC16’s balanced features, and PIC18’s robust performance for innovative projects., we typically implement:

1. Cooperative Multi-Tasking

2. Interrupt-DrivenImplementing Interrupt-Driven Systems for Real-Time ApplicationsImplementing Interrupt-Driven Systems for Real-Time ApplicationsLearn to configure and optimize PIC microcontroller interrupts for real-time performance. Enhance responsiveness and efficiency using best practices. Task Scheduling

Below is a conceptual table of tasks and their scheduling intervals:

Task NameFunctionIntervalNotes
LED Blink FastToggles fast LED (RC0)100 msQuick status blink
LED Blink SlowToggles slow LED (RC1)500 msGeneral indication
Sensor ReadingReads analog sensor50 msUses ADC to get sensor values
UART CommunicationSends data to serial portOn demand / ISRCould be event-driven by interrupt flag

Project 3: Simple Cooperative Task Scheduler🔗

Let’s illustrate a cooperative task scheduler. You will use timer interruptsImplementing Interrupt-Driven Systems for Real-Time ApplicationsImplementing Interrupt-Driven Systems for Real-Time ApplicationsLearn to configure and optimize PIC microcontroller interrupts for real-time performance. Enhance responsiveness and efficiency using best practices. to keep track of time slices, but each task will run in the main loop according to its scheduled interval.

1. Global Counters: Maintain counters for each task based on the interruptImplementing Interrupt-Driven Systems for Real-Time ApplicationsImplementing Interrupt-Driven Systems for Real-Time ApplicationsLearn to configure and optimize PIC microcontroller interrupts for real-time performance. Enhance responsiveness and efficiency using best practices. routine.

2. Task Functions: Write modular functions for each task.

3. Main Loop: Checks which tasks are due to run and executes them in a round-robin fashion.

Example Code Skeleton

// Global counters updated in the ISR
volatile unsigned int ledFastCounter = 0;
volatile unsigned int ledSlowCounter = 0;
volatile unsigned int sensorCounter  = 0;
void __interrupt() ISR(void) {
    if (T0IF) {
        T0IF = 0;
        TMR0 = 256 - 125; // ~1ms reload
        ledFastCounter++;
        ledSlowCounter++;
        sensorCounter++;
    }
}
// Task: Fast LED (toggle every 100 ms)
void taskLedFast(void) {
    static unsigned int lastToggle = 0;
    if (ledFastCounter - lastToggle >= 100) {
        LATCbits.LATC0 ^= 1;  // Toggle RC0
        lastToggle = ledFastCounter;
    }
}
// Task: Slow LED (toggle every 500 ms)
void taskLedSlow(void) {
    static unsigned int lastToggle = 0;
    if (ledSlowCounter - lastToggle >= 500) {
        LATCbits.LATC1 ^= 1;  // Toggle RC1
        lastToggle = ledSlowCounter;
    }
}
// Task: Sensor Reading (every 50 ms)
void taskSensorRead(void) {
    static unsigned int lastRead = 0;
    if (sensorCounter - lastRead >= 50) {
        // Perform ADC reading...
        lastRead = sensorCounter;
    }
}
void main(void) {
    // Configure Timer0, interrupts, I/O
    // Similar to previous examples...
    TRISCbits.TRISC0 = 0;  // RC0 as output
    TRISCbits.TRISC1 = 0;  // RC1 as output
    while (1) {
        taskLedFast();
        taskLedSlow();
        taskSensorRead();
        // Other tasks...
    }
}

In this structure, each task runs quick checks to see if it’s time to do its work. If _not_ yet time, it immediately returns, allowing the other tasks to proceed.

Considerations for More Complex Systems🔗

Best Practices🔗

1. Keep ISRs Short: Do not perform heavy computations in the interrupt service routineImplementing Interrupt-Driven Systems for Real-Time ApplicationsImplementing Interrupt-Driven Systems for Real-Time ApplicationsLearn to configure and optimize PIC microcontroller interrupts for real-time performance. Enhance responsiveness and efficiency using best practices.. Instead, set flags or update counters.

2. Modular Code: Place each task in its own function for clarity.

3. Avoid Excessive PollingKey PIC Peripherals: Understanding I/O, Timers, and InterruptsKey PIC Peripherals: Understanding I/O, Timers, and InterruptsMaster PIC peripherals with this tutorial explaining digital I/O configuration, timer setup for delays and PWM, and interrupt handling for responsive designs.: Whenever possible, use interruptsImplementing Interrupt-Driven Systems for Real-Time ApplicationsImplementing Interrupt-Driven Systems for Real-Time ApplicationsLearn to configure and optimize PIC microcontroller interrupts for real-time performance. Enhance responsiveness and efficiency using best practices. for critical events instead of constant polling.

4. Testing and DebuggingDebugging and Troubleshooting Techniques with ICD and MPLAB XDebugging and Troubleshooting Techniques with ICD and MPLAB XMaster real-time PIC microcontroller debugging with MPLAB X and ICD tools. Discover breakpoint setup, variable inspection, and performance techniques.: Use MPLAB X’s debuggingDebugging and Troubleshooting Techniques with ICD and MPLAB XDebugging and Troubleshooting Techniques with ICD and MPLAB XMaster real-time PIC microcontroller debugging with MPLAB X and ICD tools. Discover breakpoint setup, variable inspection, and performance techniques. tools (ICD) to step through code and verify timing and task execution.

Conclusion🔗

Transitioning from a simple blinking LED to a system capable of handling multiple tasks is a key milestone in embedded systems development. By leveraging timer interruptsImplementing Interrupt-Driven Systems for Real-Time ApplicationsImplementing Interrupt-Driven Systems for Real-Time ApplicationsLearn to configure and optimize PIC microcontroller interrupts for real-time performance. Enhance responsiveness and efficiency using best practices., global counters, and cooperative scheduling, you can structure your PIC16Understanding PIC Family Variants: PIC12, PIC16, PIC18, and BeyondUnderstanding PIC Family Variants: PIC12, PIC16, PIC18, and BeyondExplore PIC microcontroller families: learn how PIC12’s compact design, PIC16’s balanced features, and PIC18’s robust performance for innovative projects. projects to manage larger, more complex operations without losing track of timing or system responsiveness.

This tutorial provides an essential stepping stone for multitasking with PIC16 microcontrollersUnderstanding PIC Family Variants: PIC12, PIC16, PIC18, and BeyondUnderstanding PIC Family Variants: PIC12, PIC16, PIC18, and BeyondExplore PIC microcontroller families: learn how PIC12’s compact design, PIC16’s balanced features, and PIC18’s robust performance for innovative projects.. As you continue to develop more sophisticated applications, you will find that the core concepts-task decomposition, non-blocking design, interruptImplementing Interrupt-Driven Systems for Real-Time ApplicationsImplementing Interrupt-Driven Systems for Real-Time ApplicationsLearn to configure and optimize PIC microcontroller interrupts for real-time performance. Enhance responsiveness and efficiency using best practices. management, and scheduling-pave the way to building reliable embedded systems on PIC MCUsMastering Digital I/O on PIC MCUs with Practical ExamplesMastering Digital I/O on PIC MCUs with Practical ExamplesLearn hands-on techniques for configuring and using digital I/O pins on PIC microcontrollers to control LEDs, sensors, and more in practical projects..

Stay curious, and keep experimenting with new tasks and features. With practice, these fundamentals can be adapted for numerous real-world applications where PIC16 microcontrollersUnderstanding PIC Family Variants: PIC12, PIC16, PIC18, and BeyondUnderstanding PIC Family Variants: PIC12, PIC16, PIC18, and BeyondExplore PIC microcontroller families: learn how PIC12’s compact design, PIC16’s balanced features, and PIC18’s robust performance for innovative projects. shine!

Next Step: Try adding more tasks (like communicating over a serial interface or controlling a motor via PWM). Combine everything into a cooperative scheduler to see how each new feature integrates seamlessly without disrupting existing functionality.

Author: Marcelo V. Souza - Engenheiro de Sistemas e Entusiasta em IoT e Desenvolvimento de Software, com foco em inovação tecnológica.

References🔗

Share article