Timers and Interrupts with the PIC

Objectives

The goal of this tutorial is to reintroduce you to the timer subsystem, implement a timer using an interrupt-based design, convert your pushbuttons from prior tutorials to interrupt-based functionality, and verify the results over EUSART and with a LED.

Resources

Components

Part
PIC18F47Q10 Curiosity Nano (or DIP IC)
Two breadboard-compatible SPST buttons
LED and an appropriate current-limiting resistor (or the onboard LED)

Instructions

Study the following critical information and concepts

Critical Information and Concepts Importance
In TMRX.h, find the name of the function that permits the user to define a custom callback function We will be defining and using a custom callback function that runs whenever the timer interrupt is fired.
In the PIC18F47Q10 Datasheet, identify which GPIO pins can be used to trigger interrupts. Look for “IOC” or Interrupt-on-Change to learn more Can you use any GPIO pin to trigger an input-based interrupt?
In the Microchip MPLab Code Configurator User’s Guide, determine what WPU and OD stand for. WPU is explained better on this page that discusses setting up GPIO. Look up the explanation for OD by searching for “ODC Registers” on this page to understand its use for a different PIC family. Determine the correct GPIO settings for I/O pins, as well as the best way to use these options in conjunction with a pushbutton switch.
In this tutorial on sprintf(), determine what library(or libraries) you need to include You will need to format a string.
Read through this tutorial on interrupts in MCC to understand how linking a callback works You will need to create custom callbacks to process incoming interrupts
Search for “how to print the % character with sprintf()” % is a special character and thus needs something special

MCC Setup

  1. Launch MPLAB® X and create a new project for your PIC IC.

  2. Open MCC and update the following settings:

    Please use MCC Classic. We can support it better, it has more advanced configuration options, but is also still relatively easy to use.

    1. System

      • System Clock: Configure your system clock to use the HFINTOSC at 4MHz
      • Clock divider: select a clock divider of 4.
      • Disable watchdog timer (unused)
      • Disable low-freq programming
    2. EUSART

      • Add and configure a new EUSART peripheral, taking care to select the EUSART module that connects to the PIC Curiosity Nano’s serial-to-USB connection.
      • Make sure you check the “connect to stdio” box.
    3. TMRX:

      • clock: lfintosc
      • Set timer prescaler and postscaler so that the timer period range is adjusted so that 1ms is within the range of values that can be specified for the timer period.
      • Set the timer period to 1ms
      • Enable interrupts
    4. Pin Manager: Grid View

      • Ensure that the Package matches the package of the PIC you are using
      • You will need to redefine your two pushbutton inputs to use two interrupt-capable pins as inputs. See the “critical information” section above
      • Configure the pin with the internal LED as a digital output.
    5. Pin Module

      • For the two pushbutton pins, ensure they are defined as non-analog digital inputs. After identifying what WPU and OD mean(see above), set the checkboxes correctly.
      • For IOC, select the transition type based on how you wired your pushbuttons
      • For the LED output, ensure analog is not checked.
    6. System Interrupts Page

      • Ensure the TMRX and IOC receive interrupts are enabled. Make sure the timer subsystem is at the top.
    7. Generate the MCC configuration and compile the project.

Creating a timer-based callback function

  1. Open TmrX.h

    1. Find and note the name for the function that links the timer subsystem callback to a user-defined function
    2. Find and note the name for the function that starts the timer.
  2. Open “main.c”

    1. Create a new function timer_callback() above main. it should return type void and take no input variables. Note the name of your new function
    2. Uncomment the two lines that enable global and peripheral interrupts
    3. In the main() function,
      1. link the new timer_callback() function TimerX subsystem using the Callback linking function.
      2. immediately before the infinite while loop, start the timer.

    If set up correctly, your new callback function should be called once every millisecond

  3. Custom callback code: Now, create a new global variable, initializing it to zero, for storing the time in milliseconds. Create a second global variable (also initialized to zero) to store the time in seconds.

    1. Implement custom code in timer_callback() that increments the new ms counter variable every time the function is called.
    2. Implement logic that ensures that if the current time in milliseconds is equal to or greater than one second, it:
      1. Decrements its own value by the ms equivalent of one second.
      2. Increments the seconds variable by the equivalent of one second.
      3. Toggles the on-board LED digital output

    Note: This code should work if the time in ms is any value over than 1 second.

    Note 2: This code should be written so as it doesn’t reset, clear, truncate, or lose time information.

  4. In the while loop in main(), add code that

    1. computes the current time as a float in seconds (with milliseconds behind the decimal point)
    2. fills and prints a string with the current time in seconds, starting with “t=” and ending with “s” using printf()

    Example:

     t=  45.34s
    

Note: It is bad practice to put printf() or sprintf() functions in a callback because it takes so much time to process.

Creating IOC-based callback functions

In this section you will be implementing software-based debounce logic to ensure that button pushes are not over-counted. We will create “interrupt on change” (IOC) handling that will run code whenever it detects a button-push, and use the

This section assumes you have set up IOC-based interrupts in MCC and generated a new configuration file as specified in the MCC setup section above

  1. **Identify the way to map callback functions to interrupts. **Open pin_manager.h. Find and note the name for the function that links callback functions to each of the two pins selected for your pushbutton switches. There should be two functions available, one for each pin you configured in MCC.

  2. Create your first IOC callback. Create a function pin_down() above main. it should return type void and take no input variables.

    In the main() function, link the new pin_down() function using the first callback linking function found earlier. This should be done before enabling interrupts

  3. Create your second callback. Create a function pin_up() above main. it should return type void and take no input variables.

    In the main() function, link the new pin_up() function using the first callback linking function found earlier. This should be done before enabling interrupts

  4. Write custom callback code. In pin_down() and pin_up() implement custom code that captures the current time that each button push occurs and save it into a new global variable.

  5. Implement the debounce logic. In the while loop in main(), add code that:

    1. Checks the current time against the time the up or down buttons were pushed.

    2. Checks whether those buttons are still being pushed.

    3. If the above two are both true, and if the current time reaches some internal delay (in a non-blocking way)

      1. Increments or decrements a “duty cycle” variable (once and only once per button push, using a flag or internal check variable)
      2. Resets this flag if the buttons ever change state before reaching their limit.
      3. Ensures the duty cycle neither increments over 100% nor under 0%.
    4. Fills and prints a string with the current duty cycle, starting with “DC=” and ending in “%” using printf().

      Example:

       t=  45.34s
       dc= 75%
      

    Note: It is bad practice to put printf() or sprintf() functions in a callback because it takes so much time to process.

  6. Download and compile your code and demonstrate