News:

Let's find out together what makes a PIC Tick!

Main Menu

Interrupts

Started by Fanie, Sep 21, 2025, 09:59 AM

Previous topic - Next topic

Fanie

I have someone who has an encoder that output a varying clock that needs to display a different value than the actual input frequency.
The output display frequency must sometimes be lower, and sometimes be higher, and sometimes input can be 0 so output should show zero too, of course.

The input frequency range is rather low, around 10Hz (0.1ms) to max of about 1kHz (0.001ms)

I'm thinking of doing this with interrupts.
I have never used interrupts before, but it sounds good since the counters can count separate from the main program like a peripheral in the background.

Here is what I hope to achieve with an 18F14k22

When the input frequency pin goes high, interrupt.
This interrupt start timer0 (16-bit) incrementing with the internal clock.

The next input going high reads the value of timer0, 0 the timer and restart it.

Divide or multiply timer0 value with a factor (1 to 5 ?)

65536 - new timer0 value and divide by 2 so timer1 can interrupt on overflow.
(divide by 2 because input is only triggered by a going high ? which is a cycle)

Place value in timer1 (16-bit), and start count incrementing with the internal clock
Each timer1 overflow interrupts and toggle the output pin.

To sum it up, I have two interrupts, input pin, and timer1.  I'm thinking of adding a timer0 interrupt so when it overflows, the input frequency stopped and output = 0 ?

Questions  ???

Is this possible to do ?

Can there be different interrupt handling routines ? - Say one for the input pin change, one for the timer0, and one for the timer1 ?
Or must they all be in the same one interrupt routine ?

Can more than one interrupt occur at the same time ? or do they (perhaps always ?) occur in sequence ?

As I understand the interrupt routine works like a gosub (Context Save), and return (Context Restore) with a Retfie 1 ?

Thank you.

RGV250

Hi,
QuoteCan there be different interrupt handling routines ? - Say one for the input pin change, one for the timer0, and one for the timer1 ?
Or must they all be in the same one interrupt routine ?

Can more than one interrupt occur at the same time ? or do they (perhaps always ?) occur in sequence ?
I rarely use them but the first question, at the start of the interrupt you can use If xx bit is set then do xx and so on.
On the second Q, it is possible for 2 to happen at the same time but you can process one and then check the other bit for true or exit the interrupt.

Not able to show an example at the moment and I am sure others will do it way better than me anyway.

Bob

John Drew

Hi Fanie,
Adding to Bob's comments.
1) I've used two separate Interrupts. One with high priority and one with low priority. I suppose the names make it clear.
These are described in the manual.
The high priority  has preference and might for example be used as part of a clock or measuring a pulse width.
The low priority might be responding to a button press. Not too critical but you don't want to miss it.
2) Both of the above can be used to respond to a number of interrupts by checking the Interrupt flag.
e.g if timer1flag then do something. Or maybe
e.g. If timer2flag then do something.
etc providing there's a flag you can test it can sit in a series of if...then or elseif...then

Cheers from Oz,
John

trastikata

#3
Hello Fanie, can you change the MCU?

This PIC has oly one PWM module and you can offload the MCU and build what you want with 2 PWM modules.

If you are stuck with this PIC, use the PWM instead either to generate the output frequency or to count the input frequency.

Fanie

#4
Quote from: trastikata on Sep 21, 2025, 10:52 AMThis PIC has oly one PWM module and you can offload the MCU and build what you want with 2 PWM modules.

How would one do it with 2 PWM's ?
I don't think PWM can work at such low frequency unless the clock speed is radically reduced...


Fanie

So if you get an interrupt, the program jumps to the interrupt handling routine.
You scan the interrupt flags, and handle each.
Then return to the main program.
Sounds very easy...

trastikata

#6
Quote from: Fanie on Sep 21, 2025, 11:50 AMHow would one do it with 2 PWM's ?
I don't think PWM can work at such low frequency unless the clock speed is radically reduced...

Sorry I should have been more specific, I call everything PWM ...

but instead of the classic PWM set-up, use external low-frequency clock source for Timer1 and in Compare mode set the module to toggle the CCP output when a match occurs. This way you can create any low frequency output independent of the main oscillator.

as for the frequency counter - use the second module in Count mode with Timer3 and set its clock to come from Timer1 clock source and you will have also low frequency hardware counter.

At low 1 Hz you won't have problem with the Interrupts but if you have to handle the upper limit for both input and output at 1000 Hz then you will have some lag in the code if more tasks are required to be done.

P.s. Some PICs have clock output module which can generate quite low clocks and can be externally routed to the Timer1 pin which also has a prescaller thus you can have low frequency clock without the need of the addition of low frequency external crystal for Timer1.

Fanie

Typical  :(
From the manual P63
QuoteThere are twelve registers which are used to control
 interrupt operation. These registers are:
 • RCON
 • INTCON
 • INTCON2
 • INTCON3
 • PIR1, PIR2
 • PIE1, PIE2
 • IPR1, IPR2

I count only ten

Which are the other two ?

Fanie

#8
I got the interrupt on RA.5 working, sort of.

Something causes multiple pulses on the output.
The input is only 73Hz

Even if I single pulse the input with a tweezer then the output also looks like that, multiple pulses.
If I output a fixed delay the output shows correct.
Can it be I get multiple interrupts in quick succession ?

INTHandler:

    Context Save        ' Save any variables used in the interrupt

        ' Int on pin change Port A5
        If INTCON.0 = 1 Then     ' identify the int source
           Toggle Fout           ' change output pin status
           INTCON.0 = 0          ' reset pin int flag
        EndIf


    Context Restore       ' Restore any variables and exit the interrupt
   ' Retfie 1             ' re-enable interrupts

Freq.jpg

trastikata

By any chance Fout is on PortA or PortB?

RGV250

QuoteEven if I single pulse the input with a tweezer then the output also looks like that, multiple pulses.
Contact bounce?

Bob

Fanie

Quote from: Fanie on Sep 21, 2025, 04:32 PMI got the interrupt on RA.5 working, sort of.

Cannot be contact bounce, the input signal is from an electric encoder, I can see how it looks on the scope.

tumbleweed

With IOC you also have to read the PORTx register to clear the mismatch condition.

top204

#13
You will be better using an INT0 interrupt, or INT1 or INT2 if the INT0 pin is already being used.

Then it can be set for a falling edge or a rising edge, and it has more control.

Below is a code template for a PIC18F14K22 device I have just created, that sets up an INT0 interrupt, and a 16-bit Timer0 interrupt. I hope it helps:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' A code template to setup an INT0 interrupt and a 16-bit Timer0 interrupt on a PIC18F14K22 device.
' Using its internal oscillator at 64MHz.
'
' Written for the Positron8 compiler by Les Johnson.
'
    Device = 18F14K22                                                   ' Tell the compiler what device to compile for
    Declare Xtal = 64                                                   ' Tell the compiler what frequency the device will be operating at (in MHz)
    Declare Auto_Heap_Arrays = On                                       ' Tell the compiler to create arrays above standard variables, so assembler code is more efficient
    Declare Auto_Variable_Bank_Cross = On                               ' Tell the compiler to create any multi-byte variables in the same RAM bank. For more efficiency
    On_Hardware_Interrupt GoTo ISR_Handler
'
' Setup USART1
'
    Declare Hserial_Baud = 9600                                         ' Set the Baud rate to 9600
    Declare HRSOut1_Pin  = PORTB.7                                      ' Set the TX pin

'---------------------------------------------------------------------------------------------
' Meta-macros for INT0 on a PIC18F14K22 device
'
$define INT0_Int_Bit  INTCONbits_INT0IE                                 ' The interrupt enable/disable bit for INT0
$define INT0_Flag  INTCONbits_INT0IF                                    ' The INT0 interrupt bit flag
$define INT0_FlagClear()  INT0_Flag = 0                                 ' Clear the INT0 interrupt flag
$define INT0_Enable()  INT0_Int_Bit = 1                                 ' Enable an INT0 interrupt
$define INT0_Disable()  INT0_Int_Bit = 1                                ' Disable an INT0 interrupt
$define INT0_Rising_Edge()  INTCON2bits_INTEDG0 = 1                     ' INT0 interrupt on a rising edge
$define INT0_Falling_Edge()  INTCON2bits_INTEDG0 = 0                    ' INT0 interrupt on a falling edge

'------------------------------------------------------------------------------------------------
' Meta-macros for Timer0 on a PIC18F14K22 device
'
$define Timer0_Int_Bit  INTCONbits_TMR0IE                               ' The interrupt enable/disable bit for Timer0
$define Timer0_Flag  INTCONbits_TMR0IF                                  ' The Timer0 interrupt flag
$define Timer0_FlagClear()  Timer0_Flag = 0                             ' Clear the Timer0 interrupt flag
$define Timer0_Start() T0CONbits_TMR0ON = 1                             ' Start Timer0
$define Timer0_Stop()  T0CONbits_TMR0ON = 0                             ' Stop Timer0
$define Timer0_IntEnable()  Timer0_Int_Bit = 1                          ' Enable a Timer0 interrupt
$define Timer0_IntDisable() Timer0_Int_Bit = 0                          ' Disable a Timer0 interrupt
'
' Timer0 Prescaler Select masks for bits 0 to 2 of T0CON
'
$define cTimer0_Prescale_256 %111                                       ' Timer0 1:256 prescale mask value
$define cTimer0_Prescale_128 %110                                       ' Timer0 1:128 prescale mask value
$define cTimer0_Prescale_64 %101                                        ' Timer0 1:64 prescale mask value
$define cTimer0_Prescale_32 %100                                        ' Timer0 1:32 prescale mask value
$define cTimer0_Prescale_16 %011                                        ' Timer0 1:16 prescale mask value
$define cTimer0_Prescale_8 %010                                         ' Timer0 1:8 prescale mask value
$define cTimer0_Prescale_4 %001                                         ' Timer0 1:4 prescale mask value
$define cTimer0_Prescale_2 %000                                         ' Timer0 1:2 prescale mask value

'---------------------------------------------------------------------------------------------
' Meta-macros for General interrupts on a PIC18F14K22 device
'
$define Int_Global_Enable() INTCONbits_GIE = 1                          ' Enable global interrupts
$define Int_Global_Disable() INTCONbits_GIE = 0                         ' Disable global interrupts
$define Int_Periph_Enable() INTCONbits_PEIE = 1                         ' Enable peripheral interrupts
$define Int_Periph_Disable() INTCONbits_PEIE = 0                        ' Disable peripheral interrupts

'---------------------------------------------------------------------------------------------
' Calculate the value to place into the TMR0L\H registers in order to achieve a certain overflow interrupt rate (in us)
' The calculation will also work for other 16-bit timers
'
    $define Timer0_cFOSC $eval (_xtal / 4)                              ' The fOSC of the microcontroller device (_xtal / 4)
    $define Timer0_cPrescalerValue 64                                   ' The prescaler used for the timer (must be the same prescaler as used for the timer's setup)
    $define Timer0_cMicroSeconds 10000                                  ' Interrupt rate (in uS). Note exact interrupt timings may not always be possible
    $define Timer0_cTweakValue 0                                        ' Holds a tweak value for the timer calculation
    $define Timer0_cValue $eval ((65536 + Timer0_cTweakValue) - ((Timer0_cMicroSeconds / Timer0_cPrescalerValue) * Timer0_cFOSC))
$if Timer0_cPrescalerValue >= Timer0_cMicroSeconds
    $error "Timer0_cPrescalerValue is too large for the value in Timer0_cMicroSeconds"
$endif
$if Timer0_cValue > 65534
    $error "Timer0_cValue is too large for the interrupt duration"
$elseif Timer0_cValue <= 0
    $error "Timer0_cValue is too small for the interrupt duration"
$endif
'
' Create Global variables here
'
    Dim tINT0_Triggered As Bit                                          ' Holds 1 if an INT0 interrupt was triggered
   
    Dim wTimer0_SFR As TMR0L.Word                                       ' Create a 16-bit SFR from TMR0L and TMR0H
     
'---------------------------------------------------------------------------------------------
' The main program starts here
' Setup an INT0 interrupt and transmit text when a falling edge occurs on it
'
Main:
    Setup()                                                             ' Setup the program and any peripherals
    HRSOut1Ln "Start"                                                   ' Transmit, to make sure the device is operating at the correct speed, and the terminal is receiving
   
    Do                                                                  ' Create a loop
        If tINT0_Triggered = 1 Then                                     ' Was an INT0 triggered?
            tINT0_Triggered = 0                                         ' Yes. So clear the indicator flag
            HRSOut1Ln "INT0 Triggered"                                  ' Transmit that the INT0 was triggered
        EndIf
    Loop                                                                ' Do it forever

'---------------------------------------------------------------------------------------------
' Setup the program and any peripherals
' Input     : None
' Output    : None
' Notes     : None
'
Proc Setup()
    Osc_64MHz()                                                         ' Setup the device to operate at 64MHz using its internal oscillator  

    tINT0_Triggered = 0                                                 ' Reset the INT0 triggered flag
    PinInput PORTA.0                                                    ' Make pin PORTA.0 an input for INT0
    Timer0_Init()                                                       ' Initialise Timer0
    INT0_Falling_Edge()                                                 ' Set INT0 to interrupt on a falling edge
    INT0_Flag = 0                                                       ' Clear the INT0 interrupt flag
    INT0_Enable()                                                       ' Enable an INT0 interrupt 
    Timer0_FlagClear()                                                  ' Clear the Timer0 interrupt flag
    Timer0_IntEnable()                                                  ' Enable a Timer0 interrupt
    Int_Periph_Enable()                                                 ' Enable peripheral interrupts
    Int_Global_Enable()                                                 ' Enable global interrupts  
EndProc

'---------------------------------------------------------------------------------------------
' Setup the device to operate at 64MHz using its internal oscillator
' Input     : None
' Output    : None
' Notes     : For a PIC18F14K22 device
'
Proc Osc_64MHz()
    OSCCON = $70
    OSCCON2 = $04
    OSCTUNE = $40
EndProc

'---------------------------------------------------------------------------------------------
' Setup Timer0 for an overflow
' Input     : None
' Output    : None
' Notes     : For a PIC18F14K22 device
'           : Does not enable a Timer0 interrupt
'
Proc Timer0_Init()
    T0CONbits_T08BIT = 0                                                ' Enable 16-bit Timer mode before loading TMR0L\H
    wTimer0_SFR = Timer0_cValue                                         ' Load SFRs TMR0L\H with the value required for an overflow time (see preprocessor calculation above)
    T0CON = %10010000                                                   ' Prescaler enabled. 16-bit operation
    T0CON = T0CON | cTimer0_Prescale_64                                 ' Set the the prescaler to 1:64 (the same prescaler as the calculation above)
    Timer0_Start()                                                      ' Start Timer0   
EndProc                                                 

'---------------------------------------------------------------------------------------------
' Interrupt Handler
' Input     : None
' Output    : None
' Notes     : Interrupts on an INT0 and a Timer0 overflow
'
ISR_Handler:
    Context Save                                                        ' Save any SFRs and compiler system variables used within the interrupt
'
' Check for a INT0 interrupt
'    
    If INT0_Int_Bit = 1 Then                                            ' Is an INT0 interrupt enabled?
        If INT0_Flag = 1 Then                                           ' Yes. So has an INT0 triggered the interrupt?
            '
            ' Yes. So do something here
            '
            tINT0_Triggered = 1                                         ' Indicate that an INT0 interrupt occured (must be reset in the main program)
            INT0_FlagClear()                                            ' Clear the INT0 interrupt flag    
        EndIf
    EndIf
'
' Check for a Timer0 overflow interrupt
'   
    If Timer0_Int_Bit = 1 Then                                          ' Is a Timer0 overflow interrupt enabled?
        If Timer0_Flag = 1 Then                                         ' Yes. So has Timer0 triggered an interrupt?
            '
            ' Yes. So do something here
            '
            wTimer0_SFR = Timer0_cValue                                 ' Load SFRs TMR0L\H with the value required for an overflow time
            Timer0_FlagClear()                                          ' Clear the Timer0 interrupt flag
        EndIf
    EndIf
   
    Context Restore                                                     ' Restore any SFRs and compiler system variables used within the interrupt, and exit it

'---------------------------------------------------------------------------------------------
' Setup the config fuses for the internal oscillator running at 64MHz, on a PIC18F14K22 device
'
Config_Start   
    FOSC = IRC                                                          ' Use the internal RC oscillator
    PLLEN = Off                                                         ' 4xPLL is under software control
    PCLKEN = On                                                         ' Primary clock enabled
    FCMEN = Off                                                         ' Fail-Safe Clock Monitor disabled
    IESO = Off                                                          ' Oscillator Switchover mode disabled
    PWRTEN = Off                                                        ' Power-up Timer disabled
    BOREN = SBORDIS                                                     ' Brown-out Reset enabled in hardware only (SBOREN is disabled)
    BORV = 19                                                           ' Brown Out Reset Voltage set to 1.9 V nominal
    WDTEN = Off                                                         ' Watchdog Timer is controlled by SWDTEN bit of the WDTCON register
    WDTPS = 32768                                                       ' Watchdog Timer Postscale is 1:32768
    HFOFST = On                                                         ' HFINTOSC starts clocking the CPU without waiting for the oscillator to stablise
    MCLRE = On                                                          ' MCLR pin enabled. RA3 input pin disabled
    STVREN = On                                                         ' Stack full/underflow will cause Reset
    LVP = Off                                                           ' Single-Supply ICSP disabled
    BBSIZ = Off                                                         ' 1kW boot block size
    XINST = Off                                                         ' Extended Instruction Set disabled (Legacy mode)
    Debug = Off                                                         ' Background debugger disabled. RA0 and RA1 configured as general purpose I/O pins
    Cp0 = Off                                                           ' Block 0 not code protected
    CP1 = Off                                                           ' Block 1 not code protected
    CPB = Off                                                           ' Boot block not code protected
    CPD = Off                                                           ' Data EEPROM not code protected
    WRT0 = Off                                                          ' Block 0 not write protected
    WRT1 = Off                                                          ' Block 1 not write protected
    WRTC = Off                                                          ' Configuration registers not write protected
    WRTB = Off                                                          ' Boot block not write protected
    WRTD = Off                                                          ' Data EEPROM not write protected
    EBTR0 = Off                                                         ' Block 0 not protected from table reads executed in other blocks
    EBTR1 = Off                                                         ' Block 1 not protected from table reads executed in other blocks
    EBTRB = Off                                                         ' Boot block not protected from table reads executed in other blocks
Config_End

The overflow interval of the timer is easily changed using the preprocessor calculations, and can be enabled or disabled using one of the meta-macros.

Notice how the meta-macros for the peripherals make things a lot easier to manage in the main program listing? And to clear the main code listing, they can be placed in an include file, so all that is in the main program is the program code itself, and the clutter is hidden. It also makes things easier when swapping for a different device, because the meta-macros can remain in the main program, but their contents can be changed for the different device, if they require different SFR and bit names.

Fanie

Quote from: top204 on Sep 21, 2025, 06:41 PMYou will be better using an INT0 interrupt, or INT1 or INT2 if the INT0 pin is already being used.

Then it can be set for a falling edge or a rising edge, and it has more control.

Les, it looks like I'm getting multiple interrupts, hence the many output pulses.
This 18F14k22 is different than the other k pics.
Is it possible that the Context Save Context Restore have an oversight ?
Even if I disable interrupts in the interrupt routine I still get multiple outputs as in the picture.

The interrupt is extremely simple.  All port A and B interrupts, and all others have been disabled except RA.5
If I enable pull-ups, then the output only outputs a long continuous fast frequency, while the input RA.5 is at 0V.

top204

#15
The PIC18F14K22 device is no different to the other 18FxxK devices.

All the Context Save and Restore do is load SFRs or system variables into other variables for safely. Nothing complex and nothing that can cause problems. But it only saves the SFRs and variables if they are used, and it does not save/restore all SFRs, only the important ones such as FSRx and PRODx etc... So if the interrupt is manipulating TRIS or PORT SFRs or pins, they should be saved/restored as well. For example:

Context Save PORTA, TRISA
'
' Interrupt code
'
Context Restore

Also remember, the Toggle command will set a pin to output as well, before complementing it, and this will interfere with things at a very small scale. For more control, either use a complement character, or, on an 18F device, use the Btg mnemonic, having previously set the required pin as an output, in the program's setup.

However, without seeing the rest of your program, you must also make sure that it is not interfering with the port as well, otherwise, the older mechanism of IOC for a set of pins, may fire the interrupt. .

That is why the INTx mechanism is better, because there is more control over it. The older IOC on multiple pins will not work well on a smaller device, because the pins are also multiplexed with other items within it, and these can cause problems as well, unless switched off.

Fanie

I tried everything I can to get the portA.5 pin working and nothing made any difference  :(
Thank you for taking the time !

I have since changed the portA.5 input to Ext int pin 1 (portA.1) which is also the programmer clock.

Triggers on rising edge, it works, although the output is half the input frequency.
The main program driving a display and analog input and it works flawless so far while the interrupt does it's thing.

I can now start on the two 16 bit timers...
The display is so I can see the count in the two 16 bit registers, and not working blind.


Fanie

Can someone please explain to me how this works ?  8 bit manual P467

Dim wTimer0 As TMR0L.Word         ' Create a 16-bit Word from registers TMR0L/H
Does this mean the value in the timer0 16 bit counter will be in wTimer0 ?

Pepe


Fanie

#19
Can timer0 be counting from the 30kHz instead of the system clock ?

It seems the timer0 overflows ? and I'm getting very erratic readings.
The timer0 count is supposed to be changing from low interrupt frequency (high count) to high interrupt frequency (low count).  Even as high as 50kHz from a function generator it seems still to overflow.
Even when I run the pic at 4MHz and the prescaler at 256 the timer0 count does not display correctly.  The interrupt time is stable but not the supposed counts in the timer0.  I have even toggled the interrupt Edge Select in the interrupt routine to make the interrupt do double time.  Input and output is the same confirming the interrupt time is correct.

I don't even know if I zero the timer0 correctly.  The data sheet claims you put a value in the lowbyte and by magic the highbyte appears as well.  I assume when clearing the timer0 lowbyte that the highbyte will clear as well ?

I have to say that some things in the pics are extremely limiting, and you have to work within specific limits or it won't work.  Low frequency PWM is one, I have not once used the PWM peripheral in one of my apps.  Same with these timers it seems.  Everything feels fiddly.

If the prescaler is set to 256, does it count every 256'th count into timer0 ?  Who knows what the thing does.

I feel more or less like these guys...
https://youtube.com/shorts/t9fyZNiTyF4?feature=shared