Timers and Interrupts on an LCD screen 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 on your LCD.

This assignment builds off the LCD and Button Tutorial, with minor hardware modifications. Thus it is suggested that you keep all the components on the board as they were from that tutorial.

The two exceptions are the two pushbutton pins. You will need to shift to using GPIO registers that support interrupt-on-change (IOC). See critical information section above for details.

If you haven’t completed the LCD and pushbuttons tutorial, we strongly suggest you review that first.

Resources

Prior to the Demonstration of Proficiency

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. Open the MPLabX project from the LCD and Button Tutorial, and comment out the lines of code for incrementing and decrementing the PWM duty cycle in main(). This will be done in a different way.

    or

    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

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

      1. clock: lfintosc
      2. 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.
      3. Set the timer period to 1ms
      4. Enable interrupts
    3. Pin Manager: Grid View

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

      1. 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.
      2. For IOC, select the transition type based on how you wired your pushbuttons
      3. For the LED output, ensure analog is not checked.
    5. System Interrupts Page

      1. Ensure the TMRX and IOC receive interrupts are enabled. Make sure the timer subsystem is at the top.
    6. 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 a 16-character string with the current time in seconds, starting with “t=” and ending with “s”
    3. prints that string to the top line of the LCD.

    Example:

     "t=  45.34s      "
    

    Note: the blank spaces within quotes correspond to valid lcd positions

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

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. Open pin_manager.h

    1. 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 a function pin_down() above main. it should return type void and take no input variables.

    1. In the main() function,

      1. link the new pin_down() function using the first callback linking function found earlier. This should be done before enabling interrupts
  3. Create a function pin_up() above main. it should return type void and take no input variables.

    1. In the main() function,

      1. link the new pin_up() function using the first callback linking function found earlier. This should be done before enabling interrupts
  4. Custom Callback code

    1. 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. 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 currently pushed

    3. if the above two are true, increments or decrements the duty cycle (once and only once) if the current time reaches some internal delay (in a non-blocking way). Resets this check if the buttons ever change state before reaching their limit

    4. Fills a 16-character string with the current duty cycle, starting with “DC=” and ending in “%”

    5. prints that string to the bottom line of the LCD.

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.

  1. Download and compile your code and demonstrate