Try   HackMD

EEE 158 1st Sem AY 2021-2022

Module 3: PIC32 General-Purpose I/O v2

Notes before proceeding:

  • The videos mentioned in this document are just supplementary and optional.
  • The lengths of the videos are indicated inside the parentheses for your reference.
  • The given sample codes are for reference only. These are not meant to be run as they are.
  • The function of SFRs here is summarized; check the datasheet for more information.

Objectives

  • Learn about the microcontroller's GPIO module.
  • Use the GPIO module to interface with simple I/O devices (pushbutton and LED).
  • Learn how to use MPLAB X IDE's Logic Analyzer tool.
  • Learn how to use MPLAB X IDE's Stimulus tool.
  • Implement software and module-based delays.

Introduction

In this exercise, we will explore the functionality of PIC microcontrollers in interfacing with simple input/output devices such as pushbuttons and light-emitting diodes (LEDs). From the development board, general-purpose input/output (GPIO) pins can be considered the simplest of peripherals. All I/O ports have the following registers directly associated with the configuration of the port, where x is a letter that denotes the particular I/O port. Each I/O pin in the development board has an associated bit in the TRIS, PORT, ANSEL, and LAT registers.

TRISx Port 'x' Data Direction Control Register
The TRISx register control bits determine whether each pin associated with the I/O port is an input or an output. If the TRIS bit for an I/O pin is a `1`, then the pin is an input. if the TRIS bit for an I/O pin is a `0`, then the pin is configured for an output.
1 - input
0 - output
PORTx I/O Port Register
Data on an I/O pin is accecssed via a PORTx register. A read of the PORTx register reads the value of the I/O pin, while a write to the PORTx register writes the value to the port data latxch. This will also be reflected on the PORTx pins if the TRISx is configred as an output and the mutliplexed peripherals (if any) are disabled.
LATx Port 'x' Data Latch Register
The LATx register associated with an I/O pin eliminates the problems that could occur with read-modify-write instructions. A read of the LATx register returns the values held in the port output latches instead of the values of the I/O pins.A read-modify-write operation on the LATx register, associated with an I/O port, avoids the possibility of writing the input pin values into the port latches. A write to the LATx register has the same effect as a write to the PORTx register.
ANSELx Port 'x' Analog Select Register
The ANSELx register controls the operation of the analog port pins. The port pins that are to function as analog inputs must have their corresponding ANSELx and TRISx bits set. For a pin to be configured as digital I/O, the ANSEL bit must be set as `0`. For a pin to be an analog I/O, the ANSEL bit must be set as `1`.

Rule of Thumb: READ on PORTx, WRITE on LATx

  • Reading PORTx reads the voltage (and consecutively, the logic value) seen at the I/O port. Note that the value read will only ever be '1' or '0' depending on if the voltage input is higher or lower than the internally set threshold.
  • The PORTx register is not meant to be written on.
  • Reading LATx reads the last written value onto the port, instead of the actual values on the pins. Depending on how fast the internal workings of the port are, this may not accurately be the current value on the port.
  • Writing on the LATx sets the value on the latch register of the associated port. This allows the port to maintain that value until overwritten by the program.

PIC32MM GPIO Configuration

The PIC32MM0064GPL036 microcontroller has 3 GPIO ports. The available pins per port are shown below. See p.7 of the given PIC32MM family datasheet for the pin mapping.

  • Port A: PORTA[9], PORTA[4:0]
  • Port B : PORTB[15:0]
  • Port C : PORTC[9:8], PORTC[3:0]

Each bit of these ports can act as an input or output, depending on the TRISx value. If TRISB[7] is 0 while the rest of the TRISB bits are 1, then only RB7 is an output pin. If TRISA is 0x000C then only RA1 and RA3 are inputs while the rest of the pins of port A are outputs.

Onboard GPIO devices

In the given development board, the general-purpose LEDs LED1 and LED2 are already connected to pins RA0 and RC9. On the other hand, the pushbuttons S1 and S2 are connected to pins RB7 and RB13, respectively. Shown below are the exact connections:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Figure 1. Schematic for onboard GPIO Devices of the PIC32MM Curiosity Development Board.
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Figure 2. (Left) open-drain digital output with external pull-up. (Right) Digital input with internal pull-up resistor enabled.

Digital Outputs

There are two ways to use digital outputs: buffered and open-drain. Buffered digital outputs are the more intuitive option where if the associated LATx bit is 1, the output pin is driven to

VOH, usually
3.3V
. Otherwise, if the associated LATx bit is 0, the output pin is driven to
VOL
, usually
0V
or GND.

In open-drain mode, the output pin acts as an open switch if the associated LATx bit is 1. Thus, if an external pull-up resistor is used, as shown in the left schematic below, then the voltage output of the pin can be increased up to the device limit of

5V. Otherwise, the output pin will be pulled to GND by the activated switch if the associated LATx bit is 0. To enable open-drain mode for any GPIO pin, the corresponding bits in the ODCxregister needs to be set to 1.

MPLAB X IDE's Logic Analyzer

The Logic Analyzer is a feature of the MPLAB X IDE simulator that acts like a real logic analyzer, displaying the digital levels of all the selected pins during a selected period. It is useful for checking the behavior of functions.

To use the Logic Analyzer, do the following:

  1. Enable instruction tracing.
    • Open the Project Properties dialog box by right-clicking on the project file. Make sure that the Connected Hardware Tool is set to Simulator.
    • Under Conf, select the Simulator option.
    • In the Options categories dropdown, choose Trace. This should show the Trace options. By default, this is set to Off.
    • In the Data Collection Selection dropdown, choose Instruction Trace. You should see something similar to the figure below.
    • Click Apply and then OK.
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Figure 3. Options for Simulator section in Project properties dialog box.
  1. Open the Logic Analyzer window.
    • Under Window, go to Simulator > Logic Analyzer. It should open a Logic Analyzer window in the lower part of your screen by default.
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Figure 4. Logic Analyzer window.
  1. Select the pin to display.
    • Look for a wrench-and-hammer icon for the Settings. Click on the icon. A Logic Analyzer Settings window should pop up.
    • On the Add/Delete Pins tab, select the pin you want to observe from the Available Pin selection.
    • Add the pin by clicking the forward icon.
    • Click OK.
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Figure 5. Logic Analyzer Setting popup window.
  1. Run the Debugger tool.
    • Click the Debug Main Project button.
    • Once the Debugger reaches a breakpoint or if you pause the simulation, the Logic Analyzer contents should update.

Example: Blinky Blinky

For the onboard LEDs, we can see back in Figure 1 that we just need to drive the corresponding pin to a high enough value to turn them on. We should be able to do that by configuring the connected GPIO pins as outputs and setting the output value to 1. To achieve this for LED1, we can put the following in our code:

#include <stdio.h>
#include <xc.h>

int main(void)
{
    TRISAbits.TRISA0 = 0;   // Alternatively TRISA &= 0xFFFE;
    LATAbits.LATA0 = 1;     // Alternatively LATA |= 0x0001;
}

Add a breakpoint in the line that sets the LATA0. Then, debug the project by running the Debugger tool. When the debugger reached the breakpoint, click on the Step Into command once. You should see something similar to the figure below.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Figure 6. Change in RA0 shown in Logic Analyzer.

As you can see, the pin is driven to

3.3V which is
VOH
. If you continue to run the debugger tool, you'll see that the pin will continue to be at that voltage.

If you already have your own development board, you can also try to program your device with this code. The LED1 should light up. If it didn't work, and you are sure that you typed in the right commands, make sure your PIC is properly connected. You can try re-uploading your code again after disconnecting and reconnecting your cable. If it still doesn't work, contact your lab handler immediately.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Figure 7. PIC32MM Curiosity Development Board's LED1 turned on.

To get a blinking LED, we need to turn it off and then on again in a loop. If we are using C, we should have something like this:

TRISAbits.TRISA0 = 0;   // Alternatively TRISA &= 0xFFFE;
while (1) {
    LATAbits.LATA0 = 1;     // Alternatively LATA |= 0x0001;
    LATAbits.LATA0 = 0;     // Alternatively LATA &= 0xFFFE;
}

Did that work? Well it kinda works as shown in Figure 8 but it blinks too fast for any normal person to notice (you might notice that LED is a bit dimmer though). It is blinking too fast because our output port values change as soon as the commands are issued.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Figure 8. RA0 flip-flopping between 3.3V and 0V.

If our PIC executes commands at

8MHz, that means that the LED is toggled every
125ns
which is practically unnoticeble to the naked eye. To make things more interesting, let's try slowing that down with a delay:

TRISAbits.TRISA0 = 0;   // Alternatively TRISA &= 0xFFFE;
while (1) {
    LATAbits.LATA0 = 1;     // Alternatively LATA |= 0x0001;
    for (int i = 0; i < 100000; i++); // Delay loop
    LATAbits.LATA0 = 0;     // Alternatively LATA &= 0xFFFE;
    for (int i = 0; i < 100000; i++); // Delay loop
}

We should now see that our LED is blinking at a steady rate, just in time for Christmas :)

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Figure 9. PIC32MM Curiosity Development Board's LED1 blinking.

Digital Inputs

The digital input pins of the PIC32, when configured correctly, are connected to Schmitt Triggers which is what is read through the PORTx registers. A regular comparator outputs a digital high signal once its input reaches a certain threshold and outputs a digital low signal otherwise. A Schmitt Trigger is a modified comparator where there are two thresholds depending on the current output. The additional threshold makes the port less susceptible to noise as seen in the right plot in Figure 3. You can find on the datasheet the specific threshold values for the PIC32 input pins in the datasheet.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Figure 10. Digital output with a noisy input signal from a comparator (left) and Schmitt Trigger (right).

Pushbutton Pullups

Getting both a high and a low voltage input to a single pin might make you think that two pushbuttons might be needed. Fortunately, there is a way to do this with just a single pushbutton through the use of internal pull-up resistors. Shown in Figure 2 is a diagram describing the configuration of a digital input configured with an internal pull-up resistor to 3.3V. If the connected switch is open, the input pin will read as the pull-up voltage of 3.3V. This in turn represents a logic value of HIGH. Closing the switch would in turn represent a logic value of LOW since there will now be a path to ground.

For onboard switches such as the ones in Figure 1, notice that for every pin connected to a pushbutton, there is already an external pull-up resistor to pull up the voltage to

3.3V. Again, if the switch is open, there would be no path to the ground, hence the pin voltage will be pulled up to 3.3V. This in turn would mark as a logic HIGH in our microcontroller. Similarly, a closed switch would short the two terminals of the pushbutton, resulting in a logic LOW in our microcontroller.

Pushbutton Polling

Previously we used the GPIOs as output. In this section, we will now use our GPIO module as output, so that our microcontroller can be interfaced with devices such as pushbuttons. To start, here is a sample polling program for a single pushbutton:

int main(void) {
    ANSELA = 0;                     // Set all ports of PortA as digital
    ANSELB = 0;                     // Set all ports of PortB as digital
    TRISBbits.TRISB7 = 1;           // Set RB7 to be input
    TRISAbits.TRISA0 = 0;           // Set RA0 to be output
    CNPUBbits.CNPUB7 = 1;           // Set the internal pull-up of RB7
    
    while(1) {
        if(!PORTBbits.RB7) {        // Poll for RB7 press
            LATAbits.LATA0 = 1;     // Set the LED pin to HIGH
        }
        else
            LATAbits.LATA0 = 0;     // Set the LED pin to LOW
    }
}

First, we set the appropriate GPIO configurations for our pushbutton and LED. For simplicity, we set all of the ports of PortA and PortB to be digital by setting the register values to 0. The internal pull-up of RB7 was also configured so that when the normally-open pushbutton is not pressed, the pin voltage will be high enough to register as HIGH.

Remember from the previous sections that if a push-button is pressed, the switch is closed. Hence, the microcontroller will read a voltage level of 0V. Simply put, the program above continuously checks if PB1 is pressed. If a press is detected, the LED turns ON. This method of waiting for an input change is what we call "polling".

MPLAB X IDE's Stimulus

Stimulus is the input to the simulator, i.e., data generated to exercise the response of simulation to external signals. Stimulus in the simulator can be either asynchronous or synchronous. Asynchronous is generally used to refer to interrupts that may occur at any time during processor execution while synchronous means that the event is tied to an operation that occurs during the execution of the code. For this example, we will be using Asynchronous Stimulus which is data generated to simulate external inputs to a simulator device.

To show the Stimulus window, go to Window > Simulator > Stimulus. You should see the Stimulus window show at the lower section of the IDE. Select the Asynchronous tab. Add a row and select RB7 for the Pin and Set High as the Action. Then, add another row and also select RB7 for the Pin but this time, select Set Low as the Action.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Figure 11. Stimulus window with sample Asynchrous Stimuli.

Now, debug the sample pushbutton polling code. Then, fire the Set Low stimulus followed by the Set High stimulus. You should see the value of RA0 to correspond to the stimuli we fired using the Stimulus tool (i.e., RA0 became

VOH after we fired a stimulus that set RB7 to low and became
VOL
after we fired a stimulus that set RB7 to high). The response should be something similar to Figure 12. To better follow these changes, you can use breakpoints and the Step Into command while debugging.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Figure 12. RA0 changing value after stimuli.

Delays and Debouncing

Pushbuttons often generate spurious open/close transitions upon pressing. These may be due to mechanical and physical issues which the controller may read as multiple presses in a very short program. Instead of detecting a simple change in the pin state, we use what is called a contact bounce period in which we first wait for the read waveform to stabilize for some time.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Figure 13. Contact Bound Period


For more precise delays, we can utilize the following function prototypes:

#define SYS_FREQ 4000000

void delay_us(unsigned int us) {
    us *= SYS_FREQ / 1000000 ;
    _CP0_SET_COUNT(0);
    
    while(us > _CP0_GET_COUNT());
}

void delay_ms(int ms) {
    delay_us(ms*1000);
}

Interrupts

Previously, we have used polling to get inputs from the switches on our boards. However, you may notice that during that time we are waiting for the input, the microcontroller cannot do anything else. If we ever wanted to have an LED that is blinking while waiting for the input we would probably need a not-so-simple delay loop to be implemented properly. Fortunately, we can go around this by using interrupts.

Interrupts are asynchronous (i.e. can happen anytime) events that disrupt the normal flow of your program. This allows the microcontroller to focus on a key task and attend to these events (e.g. pressing a button) as they come without needing to wait for them. Kevin Lynch provides us with an overview of interrupts in the PIC32 in this video (4:30).

PIC32MM Interrupt SFRs

Each interrupt source is defined by three groups of SFRs. Our friend Kevin Lynch also has a video on this (6:22) but below is a summary of what these SFRs do:

IECx Interrupt Enable Control
Setting the bits here to 1 will enable the interrupts associated with them. If an enabled interrupt occurs the Interrupt Service Routine (ISR) is executed by the microcontroller.
IFSx Interrupt Flag Status
Reading a value of 1 from these registers mean that the associated interrupt has occurred. While the flag of an interrupt is 1, succeeding occurrences of the interrupt will go unnoticed. Thus, it is good practice to clear the interrupt flag after the interrupt has been serviced.
IPCy Interrupt Priority Control
These registers contain bits regarding the priority of the associated interrupt. For each IPC register there are up to 4 sets of priority and subpriority bits.
IPz Interrupt Priority Bits
Each interrupt is given a priority so that the microcontroller knows which to address first if multiple occur at the same time. An interrupt that has occurred but is not yet addressed because another interrupt is being serviced is called a masked interrupt. With 3 bits associated to each interrupt, there are a total of 7 different priority levels. It is often considered that a priority level 0 is functionally disabled since the interrupt will always be masked by the main program.
ISz Interrupt Subriority Bits
For some microcontrollers, interrupts of the same priority level usually share some settings and special configurations to speed up configuration. To add some differentiation between same-level interrupts subpriority bits are used.

Note that there are 2 registers each for the enable and control bits and an interrupt would be usually associated with the same register number. On the other hand, there is a total of 11 priority registers which, more often than not, is a different register number for the same interrupt. Hence, the use of 'x' an 'y'. You may check the datasheet or reference manual for the exact mapping of each interrupt source to their bit locations.

Interrupt Service Routine (ISR)

An ISR is the function that is automatically called if an enabled interrupt occurs. Because of their special functionality, they are placed in a specific part of memory pointed at by the Interrupt Vector. The PIC32MM can choose to use a single interrupt vector in Single-Vector Mode* (SVM) or use different vectors in Multi-Vector Mode (MVM) for each interrupt source.

In MVM, the XC32 compiler automatically puts the ISR in the appropriate location in memory as long as you define it correctly:

#include <xc.h>            // XC Library for vector name definitions
#include <sys/attribs.h>    // __ISR macro definition

void __ISR (_INTERRUPT_VECTOR_NAME, IPLnYYYY) ISR_Name (void) {
    // ISR definition 
}

where:

  • void - The leftmost one is the return type of the function as it is required in C++ syntax. The last one at the end of line 1 is optional as it just says that the function does not take in any arguments.

  • __ISR - Tells the compiler that an ISR is to be written. Note the double '_' in the beginning and you need to include sys/attribs.h to avoid any compiler errors.

  • _INTERRUPT_VECTOR_NAME - Change this to the XC32 Vector Name associated with the interrupt as seen in table 7-2 of the PIC32MM datasheet. The Vector Number from the same table can also be used.

  • n - This should be replaced by the interrupt priority level which should be the same as what is stored in the corresponding IPz bits.

  • YYYY - Replaced by one of two choices of how context save and restore is done for the ISR:

    • SOFT - Use the standard software context save and restore.
    • SRS - Use the Shadow Register Set, a faster way to do context save and restore but only configured priority levels can get to use this.
  • ISR_NAME - The name of the function itself and could be anything but it is recommended that it should be at least related to what it does.

The following are a few examples of ISR definitions:

void __ISR (_CORE_TIMER_VECTOR, IPL1SOFT) Times_Up (void) {
    /* 
      ISR for the Core Timer Interrupt
        - Interrupt is priority level 1
        - Software context save and restore is used
    */
} 

void __ISR (30, IPL6SRS) Time_Out (void) {
    /* 
      ISR for the CCP1 Timer
        - Interrupt is priority level 6
        - Shadow Registers are used for context save and restore
    */
}

Configuring an Interrupt on the PIC32MM

Properly harnessing the power of interrupts may sound like a daunting task. Fortunately, one of our friends has this video on how you can properly configure and use interrupts on the PIC32 in 7 easy steps (3:47). Listed below are these 7 steps and I have also added my own 0th step which totally did not cost me 2 hours of debugging:

  1. Enable multi-vector mode (MVM). This only needs to be done once, regardless of how many interrupts you would be configuring.
  2. Define your ISR using the proper declaration syntax in the previous section.
  3. Disable all microcontroller interrupts to prevent unwanted behavior before the proper configuration is set. This step is technically unnecessary but it is good practice.
  4. Configure the specific interrupt source to properly generate the interrupt.
  5. Configure the interrupt priority and sub priority.
  6. Clear the interrupt flag in case it got triggered before or during configuration.
  7. Actually enable the interrupt by setting the appropriate IECx bits.
  8. Re-enable all microcontroller interrupts if you disabled them.

With the XC32 compiler, we have the following example of the configuration of a Change Notice interrupt:

// Step 1
void __ISR (_CHANGE_NOTICE_B_VECTOR, IPL5SOFT) CNB_ISR (void) {
    /* 
      ISR for Port B Change Notice
        - Interrupt is priority level 5
        - Software context save and restore is used
    */
}

void Interrupt_Config(){
    INTCONbits.MVEC = 1;    // MVM Mode, Step 0
    
    asm volatile ("di");    // Disable interrupts in general, Step 2
    
    CNB_Config();           // Change Notice configuration, Step 3
    IPC2bits.CNBIP = 5;     // Set CNB interrupt Priority to 5, Step 4
    IFS0bits.CNBIF = 0;     // Clear CNB interrupt flag, Step 5
    IEC0bits.CNBIE = 1;     // Enable CNB interrupt, Step 6
    
    asm volatile ("ei");    // Re-enable interrupts in general, Step 7
}

Note that Interrupt_Config() needs to be run in the main code and CNB_Config() still needs to be defined.

Change Notice Interrupt

Change Notice (CN) is one of the simplest and commonly used interrupt sources especially when dealing with inputs. As the name suggests, an interrupt is generated once the state of an input pin changes. Sir Kevin talks more about the interrupt in this short video (1:18).

PIC32MM CN SFRs

The PIC32 MM family has a CN module that generates interrupts for each port (A, B, and C) and each port has 5 amin SFRs associated with the CN:

CNCONx CN Control - Contains the ON and CNSTYLE bits
ON When this bit is 1, change notice on port x is enabled and disabled if this bit is 0.
CNSTYLE CN Style Bit
There are two available styles which are used depending on the value of this bit:
1 - Edge Style: Change notice interrupt is issued when an edge transition is affected. This style is used with the CNFx bits.
0 - Mismatch Style: Change notice interrupt is issued when the current reading on the input port value is different from the previous reading. This style is used with the CNSTATx bits.
CNEN0x CN Enable 0
For the bits in this register that are set to 1, a positive transition (i.e. from logic low to logic high) on the corresponding port bit will generate an interrupt in edge style. Similarly, a mismatch in port readings will generate an interrupt for the set bits in mismatch style.
CNEN1x CN Enable 1
For the bits in this register that are set to 1, a negative transition (i.e. from logic high to logic low) on the corresponding port bit will generate an interrupt in edge style. This register is ignored in mismatch style.
CNSTATx CN Status
Each bit in this register corresponds to the status of each bit in the port that it is associated to. If the a bit is read to be 1, then a change notice event has occurred for the corresponding pin.
CNSFxM CN Flag
Works similarly to the CNSTATx register but is only enabled when in in edge style. This flag needs to be cleared to enable succeeding CN interrupts.

Configuring and Using a CN Interrupt

To properly configure the CN module, we just need to go over all the SFRs mentioned. Additionally, the CN modules only work with pins configured as inputs so we need to consider that as well.

As an example, let us consider using the push button S1 in the PIC32MM Curiosity DevBoard. The goal is to toggle the status of LED1 each time the push button is pressed:

  1. Let us first note that S1 is connected to RB7 and we make sure that RB7 is configured as an input.
  2. We should first disable the CN module to prevent any unwanted behavior during configuration. Similar to interrupts, this is not required but it is a good habit to keep.
  3. We choose to operate in edge style to distinguish between positive and negative transitions.
  4. With the external pull-up, a button press pulls the input pin from logic high to logic low, a negative transition, so we set bit 7 of CNEN1B to 1.
  5. Similar to interrupts, we should clear the CNFB7 bit just in case anything unexpected happened.
  6. With everything set, it should now be safe to enable the CN module.
  7. Lastly, we should take note that the CNFB7 bit must be cleared every time the interrupt is triggered to allow it to trigger the next time.

The commands to do these steps are highlighted in comments in the code seen on the following page:

void __ISR (_CHANGE_NOTICE_B_VECTOR, IPL5SOFT) CNB_ISR (void) {
    /* 
      ISR for Port B Change Notice
        - Interrupt is priority level 5
        - Software context save and restore is used
    */
    IEC0bits.CNBIE = 0;     // Disable CNB interrupt
    CNCONBbits.ON = 0;      // Disable CN module

    Debounce();             // Input debounce, if necessary
    ToggleLED();            // Defined elsewhere

    CNFBbits.CNFB7 = 0;     // Step 7
    CNCONBbits.ON = 1;      // Re-enable CN Controller
    IFS0bits.CNBIF = 0;     // Clear CN interrupt flag
    IEC0bits.CNBIE = 1;     // Re-enable CN interrupt
}

void CNB_Config(){
    TRISBbits.TRISB7 = 1;   // Step 1

    CNCONBbits.ON = 0;      // Step 2
    CNCONBbits.CNSTYLE = 1; // Step 3
    CNEN1Bbits.CNIE1B7 = 1; // Step 4
    CNFBbits.CNFB7 = 0;     // Step 5

    CNCONBbits.ON = 1;      // Step 6
}

void Interrupt_Config(){
    INTCONbits.MVEC = 1;    // MVM Mode
    
    asm volatile ("di");    // Disable interrupts in general
    
    CNB_Config();           // Change Notice configuration
    IPC2bits.CNBIP = 5;     // Set CNB interrupt Priority to 5
    IFS0bits.CNBIF = 0;     // Clear CNB interrupt flag
    IEC0bits.CNBIE = 1;     // Enable CNB interrupt
    
    asm volatile ("ei");    // Re-enable interrupts in general
}

Our main function should call InterruptConfig() and create an infinite loop to make sure the PIC doesn't enter sleep mode. What's left for us to do is just define Debounce() and ToggleLED() and it should work.

But we didn't even call CNB_ISR(). How would we know it would run? Well that is the advantage of interrupts, we don't have to call the ISR. The microcontroller will do that for you as long as you correctly configured it.

It didn't work? make sure you added the missing function definitions, included all the needed libraries and set up your main function well. You might also want to double-check your code as you may have mistyped anything. One usual suspects are the ISR priority level is different from the level set in the priority bits. If you still can't get it to work, contact your lab handler immediately.

Exercise 1: Notice Me (40 points)

To make sure you're catching up with the CN interrupt, let us emulate an AND gate where, the inputs are the pushbutton states. Here are the specifications, constraints, and additional notes for this activity:

  • Program LED1 to turn on as long as S1 and S2 are pressed.
  • Assume a debounce time of
    50ms
    if you are using the physical board. Debouncing is unnecessary when using the simulator.
  • There seems to be a problem with the MPLAB stimulus tool when using the CN interrupt in edge mode. If you do not have the board yet, you should configure it to use mismatch mode instead. Note that you will have to clear the CNSTATx flags instead of the CNFx flags in this mode.
  • Add your full name, section, student number as a comment in the first few lines of your code. Name the file to be pushy_with_interrupts.c.
  • Your program should be done in C and should compile using the XC 32 compiler.
  • Your delays do not have to be exact but they have to be reasonably close.

Challenge! (60 points)

Let's apply what you have learned about simple GPIO in a microcontroller. For this exercise, you will utilize LED1 and LED2, as well as PB1. Write a program named blinky_pushy.c that would achieve the following specifications.

  • STARTUP: Upon startup, LED1 and LED2 should turn ON for 2s then OFF for 2s.
  • Pressing PB1 should do nothing during the STARTUP sequence.
  • PATTERN: After the startup sequence, the LEDs should blink in a pattern depending on the last digit of your student number. The patterns are described in the table below.
Last Digit of Student Number Pattern
0-3 Both LEDs ON for 1s → LED1 ON, LED2 OFF for 1s → LED1 OFF, LED2 ON for 1s → Both LEDs OFF for 1s → repeat
4-6 Both LEDs ON for 1s → LED1 OFF, LED2 ON for 1s → LED1 ON, LED2 OFF for 1s → Both LEDs OFF for 1s → repeat
7-9 LED1 ON, LED2 OFF for 1s → Both LEDs ON for 1s → LED1 OFF, LED2 ON for 1s → Both LEDs OFF for 1s → repeat
  • If you are just using the simulator, assume
    1μs=1s
    to view the full pattern through the logic analyzer.
  • During the pattern sequence:
    • Pressing PB1 should pause the LED blinking pattern.
    • Releasing PB1 should resume the LED blinking pattern.
  • Your program should be done in C and should compile using the XC 32 compiler.
  • Add your full name, section, student number, as a comment in the first few lines of your code.

Submission

  • Submit the zipped MPLAB project folder named eee158_lab3_[STUDENT_NUMBER], e.g. eee158_lab3_201912345.
    • Make sure the exercises (pushy_with_interrupts.c, blinky_pushy.c) are included and well documented.

Summary

In this module, we discussed the different configurations in interfacing with GPIO devices such as LEDs and pushbuttons. We used the MPLAB simulator as a means to observe the state changes when the different states of an input or output device is changed. We also used polling and interrupts in combining the functionalities of different input and output devices, as seen in the exercises given. This is a good starting point for the next exercises in EEE 158.

For this module, there are a total of 2 activities:

ActivityPoints
Exercise 140
Challenge60
Total100