News:

;) This forum is the property of Proton software developers

Main Menu

External Interrupt Example

Started by GDeSantis, Mar 18, 2023, 12:55 AM

Previous topic - Next topic

GDeSantis

A simple program that demonstrates a PIC MCU's ability to respond to an external interrupt is attached.  If no interrupt is received, LED1 continuously toggles at 50 Hz.  However, if a rising edge voltage is EXT_Interrupt_Screenshot.jpgInterrupt_Example.zip  applied to pin PORTA.2, the program jumps to the ISR Handler subroutine which executes in microseconds and then returns control to the main program.

I plan to use an interrupt scheme in conjunction with a rotary encoder on a project, but have a question.  Specifically, does it make sense to disable the external interrupt feature after the program jumps to the ISR Handler followed by enabling the feature prior to exiting the ISR Handler?  Or, is this not necessary?

GDeSantis

Not sure how the text was modified in the posting; but this is what it should have said:

EXTERNAL INTERRUPT EXAMPLE

A simple program that demonstrates a PIC MCU's ability to respond to an external interrupt is attached.  If no interrupt is received, LED1 continuously toggles at 50 Hz.  However, on the application of a rising edge applied to pin PORTA.2, the program jumps to the ISR Handler subroutine which executes in microseconds and then returns control to the main program.

I plan to use an interrupt scheme in conjunction with a rotary encoder on a project and have a question.  Specifically, does it make sense to disable the external interrupt feature after the program jumps to the ISR Handler followed by enabling the feature prior to exiting the ISR Handler?  Or, is this not necessary?

John Drew

#2
Providing it's a high priority interrupt the disabling and enabling is handled within the interrupt.
John

To be clear, with a high priority global interrupts are turned off but if it's a low priority interrupt high priority are still allowed.

tumbleweed

The 16F only has a single interrupt level, so they're all "high priority". That's as opposed to the 18F which can have two levels, high and low.

Once an interrupt occurs, you can't get another interrupt of the same priority until the ISR exits with a RETFIE instruction, so there's no need to mess with disabling/enabling the peripheral IE flag unless you specifically want that interrupt disabled when you exit. The peripheral's IF flag may get set again while you're inside the ISR, and if that happens you'll immediately return to the ISR again once the RETFIE executes.

As John says, the only time an interrupt will occur inside a running ISR is if you have an 18F and a high-priority request comes in while you're inside the low-priority ISR (assuming IPEN is set to enable intr priority)


top204

Below is a program listing that uses an IOC (Interrupt On Change) to interface with a rotary encoder via an interrupt. It is written for an 18F device, but the principle also works with other device families:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Rotary encoder reader using an IOC (Interrupt On Change) interrupt
' It never misses a rotation because the IOC always gets fired when a rotation happens
'
' Written by Les Johnson for the Positron8 BASIC compiler.
'
    Device = 18F27K42                                   ' Tell the compiler what device to compile for
    Declare Xtal = 64                                   ' Tell the compiler we are running at 64MHz

    On_Hardware_Interrupt GoTo ISR_Handler              ' Point the compiler to the Interrupt Handler routine
'
' Setup USART1
'
    Declare Hserial1_Baud = 115200                      ' Set the Baud rate for USART1
    Declare Hserout1_Pin  = PORTC.6                     ' Set the pin used for USART1 TX
    Declare HSerin1_Pin   = PORTC.7                     ' Set the pin used for USART1 RX
'
' Set the pins to use for the rotary encoder
'
    Symbol Enc_Pin1 = PORTB.0                          ' Connects to a pin on the rotary encoder
    Symbol Enc_Pin2 = PORTB.1                          ' Connects to the other pin on the rotary encoder
'
' Create variables for the rotary encode routines
'
    Dim Enc_bPinStore   As Byte Access                  ' Stores the pin conditions when the IOC interrupt triggers
    Dim Enc_wCounter    As Word Access                  ' Increments or decrements depending on the direction of the rotary encoder
    Dim Enc_tTriggered  As Bit                          ' Holds the IOC triggered flag
    Dim Enc_tDir        As Bit                          ' Holds the direction of the rotary encoder

    Symbol cAntiClockwise = 0                           ' Signals that the rotation movement is anti-clockwise
    Symbol cClockwise = 1                               ' Signals that the rotation movement is clockwise

$ifndef True
    $define True 1
$endif
$ifndef False
    $define False 0
$endif

'------------------------------------------------------------------------------------------------
' General interrupt meta-macros
'
$define IntGlobal_Enable()  INTCON0bits_GIE = 1         ' Enable global interrupts
$define IntGlobal_Disable() INTCON0bits_GIE = 0         ' Disable global interrupts

$define IntPeriph_Enable()  INTCON0bits_GIEL = 1        ' Enable peripheral interrupts
$define IntPeriph_Disable() INTCON0bits_GIEL = 0        ' Disable peripheral interrupts

'------------------------------------------------------------------------------------------------
' IOC (Interrupt On Change) meta-macros for a PIC18F27K42 device
'
$define IOC_Flag() PIR0bits_IOCIF                       ' The IOC flag
$define IOC_ClearFlag() IOC_Flag() = 0                  ' Clear the IOC flag
$define IOC_Enable()  PIE0bits_IOCIE = 1                ' Enable IOC Interrupts
$define IOC_Disable() PIE0bits_IOCIE = 0                ' Disable IOC Interrupts
'
' PORTB.0 IOC meta-macros for a PIC18F27K42 Device
'
$define IOC_RB0_Rising()  '
    IOCBN.0 = 0           '
    IOCBP.0 = 1
$define IOC_RB0_Falling() '
    IOCBN.0 = 1           '
    IOCBP.0 = 0
$define IOC_RB0_Flag() IOCBF.0
'
' PORTB.1 IOC meta-macros
'
$define IOC_RB1_Rising()  '
    IOCBN.1 = 0           '
    IOCBP.1 = 1
$define IOC_RB1_Falling() '
    IOCBN.1 = 1           '
    IOCBP.1 = 0
$define IOC_RB1_Flag() IOCBF.1

'------------------------------------------------------------------------------------------------
' The main program starts here
' Displays on a serial terminal the counts created from a rotary encoder movement
'
Main:
    Setup()                                             ' Set up the program

    Do                                                  ' Create a loop
        If Enc_tTriggered = True Then                   ' Is a rotary encoder movement ready?
            Enc_tTriggered = False                      ' Yes. So reset its flag
            If Enc_tDir = cAntiClockwise Then           ' Is Enc_tDir holding 0?
                HRSOutLn "CCW: ", Dec Enc_wCounter      ' Yes. So display that the rotation was anti-clockwise
            Else                                        ' Otherwise... The value in Enc_tDir was 1. So...
                HRSOutLn "CW:  ", Dec Enc_wCounter      ' Display that the rotation was clockwise
            EndIf
        EndIf
        DelayMS 512                                     ' Place a, silly, very large delay to demonstate the rotation counter still working
    Loop                                                ' Do it forever

'------------------------------------------------------------------------------------------------
' Setup the program and peripherals
' Input     : None
' Output    : None
' Notes     : None
'
Proc Setup()
    PinMode(Enc_Pin1, Input_PullUp)                     ' \ Make sure the encoder pins are inputs
    PinMode(Enc_Pin2, Input_PullUp)                     ' / And enable the internal pullup resistors on them

    Enc_wCounter = 0                                    ' Clear the rotation counter
    Enc_tTriggered = False                              ' Reset the rotation flag

    IOC_RB0_Falling()                                   ' IOC Interrupt on a falling edge on pin PORTB.0
    IOC_RB0_Flag() = False                              ' Clear the IOC flag for PORTB.0
    IOC_ClearFlag()                                     ' Clear the IOC interrupt flag
    IOC_Enable()                                        ' Enable IOC interrupts

    IntGlobal_Enable()                                  ' Enable global interrupts
EndProc

'--------------------------------------------------------------------------
' Interrupt handler
' Input     : None
' Output    : Variable Enc_wCounter is incremented or decremented depending on the rotation direction
'           : Enc_tTriggered is 1 if there was a movement on the rotary encoder
'           : Enc_tDir holds 0 if the rotary encoder is traveling anti-clockwise, else holds 1 if travelling clockwise
' Notes     : Uses an IOC (Interrupt On Change) on PORTB.0
'
ISR_Handler:
    Context Save

    If IOC_Flag() = True Then                           ' Is it an Interrupt On Change that triggered the interrupt?
        If IOC_RB0_Flag() = True Then                   ' Yes. So is it an IOC on PORTB.0?
            Enc_tTriggered = True                       ' Yes. So set the rotation triggered flag
            Enc_bPinStore = %00000000                   ' Clear the variable Enc_bPinStore
            Enc_bPinStore.0 = Enc_Pin1                  ' Place the condition of pin Enc_Pin1 into bit-0 of Enc_bPinStore
            Enc_bPinStore.1 = Enc_Pin2                  ' Place the condition of pin Enc_Pin2 into bit-1 of Enc_bPinStore
            If Enc_bPinStore = %00000000 Then           ' Is Enc_bPinStore 0?
                Enc_tDir = cClockwise                   ' Yes. Indicate that it is a clockwise movement
                If Enc_wCounter < 65535 Then            ' \
                    Inc Enc_wCounter                    ' | Increment the rotation counter
                EndIf                                   ' /
            Else                                        ' Otherwise...
                Enc_tDir = cAntiClockwise               ' Indicate that it is an anti-clockwise movement
                If Enc_wCounter > 0 Then                ' \
                    Dec Enc_wCounter                    ' | Decrement the rotation counter
                EndIf                                   ' /
            EndIf
            IOC_RB0_Flag() = 0                          ' Clear the IOC flag for PORTB.0
        EndIf
        IOC_ClearFlag()                                 ' Clear IOC interrupt flag
    EndIf

    Context Restore

'---------------------------------------------------------------------------------------
' Setup for internal oscillator operating at 64MHz on a PIC18F27K42 device
'
Config_Start
    RSTOSC = HFINTOSC_64MHZ     ' HFINTOSC with HFFRQ = 64 MHz and CDIV = 1:1
    FEXTOSC = Off               ' HS off
    WDTE = Off                  ' WDT Disabled. SWDTEN is ignored
    CLKOUTEN = Off              ' CLKOUT function is disabled
    PR1WAY = Off                ' PRLOCK bit can be set and cleared repeatedly
    CSWEN = On                  ' Writing to NOSC and NDIV is allowed
    Debug = Off                 ' Background debugger disabled
    FCMEN = Off                 ' Fail-Safe Clock Monitor disabled
    MCLRE = EXTMCLR             ' If LVP = 0, MCLR pin is MCLR' If LVP = 1, RE3 pin function is MCLR
    PWRTS = PWRT_Off            ' PWRT is disabled
    MVECEN = Off                ' Interrupt contoller does not use vector table to prioritise interrupts
    IVT1WAY = Off               ' IVTLOCK bit can be cleared and set repeatedly
    LPBOREN = Off               ' ULPBOR disabled
    BOREN = Off                 ' Brown-out Reset disabled
    BORV = VBOR_2P45            ' Brown-out Reset Voltage (VBOR) set to 2.45V
    ZCD = Off                   ' ZCD disabled. ZCD can be enabled by setting the ZCDSEN bit of ZCDCON
    PPS1WAY = Off               ' PPSLOCK bit can be set and cleared repeatedly (subject to the unlock sequence)
    STVREN = Off                ' Stack full/underflow will not cause Reset
    XINST = Off                 ' Extended Instruction Set and Indexed Addressing Mode disabled
    WDTCPS = WDTCPS_2           ' Divider ratio 1:128
    WDTCWS = WDTCWS_0           ' Window delay = 87.5. no software control' keyed access required
    WDTCCS = LFINTOSC           ' WDT reference clock is the 31.0 kHz LFINTOSC
    BBSIZE = BBSIZE_1024        ' Boot Block size is 1024 words
    BBEN = Off                  ' Boot block disabled
    SAFEN = Off                 ' SAF disabled
    WRTAPP = Off                ' Application Block not write protected
    WRTB = Off                  ' Configuration registers not write-protected
    WRTC = Off                  ' Boot Block not write-protected
    WRTD = Off                  ' EEPROM not write-protected
    WRTSAF = Off                ' SAF not Write Protected
    LVP = Off                   ' HV on MCLR/VPP must be used for programming
    Cp = Off                    ' PFM and EEPROM code protection disabled
Config_End

Hopefully, it will be helpful to you when you interface to your rotary encoder.

Stephen Moss

Quote from: GDeSantis on Mar 18, 2023, 12:55 AMI plan to use an interrupt scheme in conjunction with a rotary encoder on a project, but have a question.  Specifically, does it make sense to disable the external interrupt feature after the program jumps to the ISR Handler followed by enabling the feature prior to exiting the ISR Handler?  Or, is this not necessary?

If you have any other interrupts enabled then it would probably be a good idea to clear and set the GIE at each end of the ISR to stop your interrupt handler being interrupted by any new interrupt.
However, if you only have that one interrupt active then even if still active it should not trigger a new interrupt until the interrupt flag is cleared, so if you leave the Interrupt Flag set until the end of your ISR then you should not need to disable/enable the Interrupt itself.

Finally, even disregarding that and clearing the flag at the top of the ISR, whether or not another interrupt would be triggered while still inside the ISR would depend on how long the ISR takes to run compared to the time between active interrupt inputs.
For example if your ISR completes in 10uS and your rotary encoder generating the interrupt signals has say 30 positions per revolution, is manually operated and turned at a speed of 60RPM then the interrupt signal would change once every 33mS and so all things being equal you would never detect a new interrupt input during the ISR as it complete 1000 times faster then the interrupt signal changes. 

tumbleweed

#6
QuoteIf you have any other interrupts enabled then it would probably be a good idea to clear and set the GIE at each end of the ISR to stop your interrupt handler being interrupted by any new interrupt.
No, you should never manipulate the GIE bit inside the ISR.

On both the 16F and 18F, once an interrupt occurs and it branches to the ISR you can not be interrupted again by an interrupt request of equal priority. Interrupts will automatically be re-enabled when the ISR exits with a RETFIE instruction. Any interrupts that occurred while you were in the ISR will then get processed and you'll re-enter the interrupt handler.

The only time you can get interrupted once you're in an ISR is if you're using an 18F with interrupt priority enabled (IPEN=1), in which case a high-priority interrupt can interrupt a low-priority ISR. If you find that you do not want this to occur then you have the priorities set incorrectly, or you should not be using IPEN at all.

If you have multiple interrupts enabled then the ISR should check to see if the individual peripheral IF (intr request) and IE (intr enable) flags are both set, something like:
ISR_Handler:
    if PIR1.TMR1IF = 1 and PIE1.TMR1IE = 1 then
        // handle TMR1 intr
        PIR1.TMR1IF = 0
    endif
    if PIR1.TMR2IF = 1 and PIE1.TMR2IE = 1 then
        // handle TMR2 intr
        PIR1.TMR2IF = 0
    endif





top204

#7
You are correct... When an interrupt is fired, interrupts of the same priority are disabled, so altering the GIE bit serves no purpose within the interrupt handler routine. And as soon as the Retfie mnemonic is issued, interrupts are re-enabled. Also... There is no need to have two comparisons for an interrupt triggered detection. i.e.

If PIR1.TMR1IF = 1 And PIE1.TMR1IE = 1 Then

Because if the interrupt is not enabled for a particular peripheral, an interrupt event will not be triggered, thus, saving both space and time in an interrupt handler.

This type of code format seems to have come into play a few years ago with microchip examples and it serves very little purpose in the "real world". :-)

But.... If you are going to use it, use the format:

If PIE1bits_TMR1IE = 1 Then
    If PIR1bits_TMR1IF = 1 Then
        '
        ' Code for the peripheral's interrupt handling here
        '
        PIR1bits_TMR1IF = 0
    EndIf
EndIf


Notice the removal of the "And" operator, which reduces the code overhead because it does not need the comparison stack to come into play without it, so it operates faster and with less flash memory used. Also, check if the peripheral's interrupt is enabled first, so it is not required to check its flag if it is not enabled. Again, saving time.

In every single program I have written that uses interrupts, I just check the peripheral's flag:

If PIR1bits_TMR1IF = 1 Then
    '
    ' Code for the peripheral's interrupt handling here
    '
    PIR1bits_TMR1IF = 0
EndIf


If the peripheral's "generate an interrupt" bit is not set within its setup SFRs, no interrupt will be fired for that peripheral anyway, so it is not required to check for it being enabled in the handler.

tumbleweed

#8
QuoteBut.... If you are going to use it, use the format:
Thanks for the tip about removing the "and".

QuoteIf the peripheral's "generate an interrupt" bit is not set within its setup SFRs, no interrupt will be fired for that peripheral anyway, so it is not required to check for it being enabled in the handler.
If you're only using a single peripheral in the ISR, that's true.

However, if you're using multiple peripherals in the same ISR it's a good idea to check both flags. The peripheral IF flag doesn't depend on the peripheral IE being set, so if you don't check both it's possible for one to erroneously "handle" an interrupt that's currently disabled when it's actually the other peripheral that's causing the interrupt.

For example, consider the uart TXIF. It's very common to disable the IE when you're done sending a message, but the IF remains set.If you don't check for TXIE, then the next interrupt (say TMR1IF) can make you falsely think you need to handle TXIF when in reality it's been disabled.

EDIT: granted, if you never disable a peripheral interrupt using the IE then there's no reason to add the check.


tumbleweed

Also, I would have thought that both of these constructs would generate the same code...
if condition1 AND condition2 then
...

if condition1 then
  if condition2 then
  ...

In the first there's no reason to check condition2 if condition1 is false, so they're equivalent.