News:

;) This forum is the property of Proton software developers

Main Menu

RB0 External Interrupt not triggering

Started by Clayton, Apr 26, 2024, 07:11 PM

Previous topic - Next topic

Clayton

Good evening all,

I'm back with a question about using the PIC16F88 RB0 External Interrupt pin.
I'm afraid that I have not used interrupts before so I most probably have miss-interpreted the action/sequence/both on setting it up, but here goes...

The idea is to measure a very fast low-going pulse fed into the RB0/INT pin.
In normal operation this interrupt is disabled, but when the pulse (which occurs randomly) occurs, I want it to be handled by the interrupt routine, which will measure the period of the pulse.
The RB0 pin has an external soft (10K) pull up fitted to it.
I have configured the associated registers as follows:

' INTCON REGISTER SETTINGS
Symbol RBIF = INTCON.0   ' RB Port Interrupt Flag
RBIF = 0                 '
Symbol INT0IF = INTCON.1 ' RB0 External Interrupt Flag
INT0IF = 0               ' Cleared
Symbol TMR0IF = INTCON.2 ' TMR0 Overflow Interrupt Flag
Symbol RBIE = INTCON.3   ' RB Port Change Interrupt Enable
RBIE = 1
Symbol INT0IE = INTCON.4 ' RB0 External Interrupt Enable
INT0IE = 1               ' Enabled
Symbol TMR0IE = INTCON.5 ' TMR0 Overflow Interrupt Enable
Symbol PEIE = INTCON.6   ' Peripheral Interrupt Enable
PEIE = 1                 ' Enabled
Symbol GIE = INTCON.7    ' Global Interrupt Enable
GIE = 1                  ' Enabled

' OPTION REGISTER SETTINGS
Symbol INTEDG = OPTION_REG.6   ' Interrupt Edge Select
INTEDG = 0


When the detection routine is instigated, the following is setup:

                INT0IE = 1              ' RB0 External INT Enabled
                INT0IF = 0              ' Reset RB0 Interrupt Flag
                INTEDG = 0
                RBIF = 0
                PEIE = 1
                GIE = 1                 ' Enable Global Interrupts

....and then the program returns to do it's normal functions until, I was hoping, a pulse occurs on the RB0 pin, when the Interrupt routine should be called and executed.

The ISR routine itself is as follows:

ISRHandler:
Context Save
        If INT0IF = 1 Then     'RB0 EXTERNAL Interrupt flag
            MeasurePulse() 
        EndIf
        INT0IF = 0            'Reset PortB Interrupt Flag
Context Restore


When I run the program, the interrupt never occurs.
Any idea as to where I have put my fott in it :)

Thanks in advance.
Clayton

keytapper

Ignorance comes with a cost

RGV250

#2
Hi,
You have not posted your code but it looks like you have a subroutine or procedure for the pulse. As you are using PortB.0 are you using the CCP module (capture), if not I think you should have a look at that as it is good for short pulses.
I might be able to find an example for a different PIC but should give you an idea if you need it.

Bob

Clayton

#3
Hi Bob and Keytapper,

Thanks for the replies.
Bob, yes I use a routine to measure the pulse which uses PulseIn to measure the pulse width and record it.

Keytapper, I will add that and see if it helps. I think I did define the pin as an input, but will double check.

Thanks,
Clayton

top204

#4
The compiler makes interrupts very easy to use. However, the device's SFRs must be configured correctly, and the interrupt's method and peripheral chosen should be understood by reading the device's datasheet.

On the very early PIC microcontroller devices, such as the PIC16F88, the INT0 mechanism was called just INT because it did not have any other INT pins, but over the years, devices got more INT pins, so even with the early devices, it is easier to call it an INT0 interrupt and not just an INT interrupt, so the code can easily be adapted for other devices that do have more than one INT source.

It is also important to keep code listings as organised as possible, so any coding anomalies can be seen and any extra maintenance or changes to the code can be carried out easily.

Below is a program listing for a PIC16F88 device that will cause an interrupt to be triggered by a rising edge on its INT pin, which is PORTB.0 on the PIC16F88 device. Each interrupt will toggle the LED, so one rising edge on PORTB.0 will illuminate the LED, and another rising edge will extinguish it. This makes it very easy to see the interrupt working. To make the interrupt happen on a falling edge, simply change the INT0_Rising() meta-macro to INT0_Falling() within the Setup procedure, or within the interrupt itself if waiting for an opposite edge to trigger something else.

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' A demonstration of a Rising Edge INT interrupt being implemented on a PIC16F88 device.
' An LED attached to PORTA.0 will toggle when a Rising Edge is detected on the INT pin (PORTB.0)
'
' Written by Les Johnson for the Positron8 BASIC compilers.
'
    Device = 16F88                                  ' Tell the compiler what device is being compiled for
    Declare Xtal = 8                                ' Tell the compiler what speed the device will be operating at (in MHz)
    On_Hardware_Interrupt ISR_Handler               ' Point to the interrupt handler routine

'-----------------------------------------------------------------------
' Create some Interrupt meta-macros to make the program easier to understand and change
'
$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

'-----------------------------------------------------------------------
' Create some INT0 (INT) meta-macros to make the program easier to understand and change
'
$define INT0_Flag() INTCONbits_INTF                 ' The INT interrupt flag
$define INT0_ClearFlag() INT0_Flag() = 0            ' Clear the INT interrupt flag
$define INT0_Rising() OPTION_REG.6 = 1              ' Set INT for a rising edge
$define INT0_Falling() OPTION_REG.6 = 0             ' Set INT for a falling edge
$define Int_INT0_Enable() INTCONbits_INTE = 1       ' Enable the INT interrupt
$define Int_INT0_Disable() INTCONbits_INTE = 0      ' Disable the INT interrupt

    Symbol LED_Pin = PORTA.0                        ' The pin that the LED will attach to
  
'-----------------------------------------------------------------------
' The main program starts here
'
Main:
    Setup()                                         ' Setup the program and peripherals
   
    Do                                              ' Create a loop
    '
    ' Any code required goes in here
    '
    Loop                                            ' Do it forever
   
'-----------------------------------------------------------------------
' Setup the program and peripherals
' Input     : None
' Output    : None
' Notes     : None
'     
Proc Setup()             
    PinLow LED_Pin                                  ' Make the LED's pin an output low
   
    INT0_Rising()                                   ' Set INT0 for a rising edge to cause an interrupt
    INT0_ClearFlag()                                ' Clear the INT0 flag
    Int_INT0_Enable()                               ' Enable an INT0 interrupt
    Int_Periph_Enable()                             ' Enable Peripheral interrupts
    Int_Global_Enable()                             ' Enable Global interrupts
EndProc

'-----------------------------------------------------------------------
' Interrupt Handler routine
' Input     : None
' Output    : None
' Notes     : Interrupts on an INT0 edge and toggles an LED on each interrupt

ISR_Handler:
    Context Save                                    ' Save some devices SFRs and any compiler system variables used within the interrupt
   
    If INT0_Flag() = 1 Then                         ' Was it an INT0 that triggered the interrupt?
        Toggle LED_Pin                              ' Yes. So toggle the LED
        INT0_ClearFlag()                            ' Clear the INT0 interrupt flag
    EndIf
    
    Context Restore                                 ' Restore some devices SFRs and any compiler system variables used within the interrupt and exit the interrupt handler

Below, is a screenshot of the program operating in the proteus simulator:

INT_on_PIC16F88_Screenshot.jpg

keytapper

I read several time that is discouraged to call a subroutine within the ISR. Preferable to set a flag in ISR and execute upon the flag status, outside the ISR. Or even not using the interrupt, but keep looking the INT0 flag itself.
Ignorance comes with a cost

Clayton

Thank you very much for your replies.
I am learning a lot about interrupts from those replies, for which I am most grateful.
I will examine those replies, try them and will then report my results.
Appreciated extensively.
Thank you.

Clayton

Hi gentlemen (and ladies),

OK, so time for this numbnut to hang his head in shame..... ;D
After using Les's cut-down program, and it still not working, I decided to go back to my roots, so to speak.
I could not believe that there was anything wrong with the sample code, so I re-examined the test circuit..... :) Guess what?
I was triggering the WRONG pin  ;D

So, once I spotted that, and the air finished turning blue, and I connected to the right pin, It now works.......sort of :)
The interrupt now fires, the time and date is retrieved, and stored in EEPROM....but the pulse is not measured :(

Going to re-examine my code around that area now.

That all said, thanks Les and all for your help with the interrupt.

Watch this space for the final outcome..... sounds like Corry :)

RGV250

Hi,
This may not be relevant but as I mentioned in the other post, have you proved your save and recall EEPROM routines without interrupts.
I am not sure how you are calling the routines but what I and I think most would do is set a bit in the interrupt and then from the main routine call the routine and then clear the bit so it is only done one. That way you are not staying in the interrupt longer than necessary.

Bob 

Clayton

Hi Bob,

The save and recall routines previously worked without the interrupt.
However, as that previous routine made it difficult to stop the pulse measurement subroutine (via COM port command) because it was in a select case routine, I wanted to have the interrupt do the branching only when a pulse occured, thus not affecting the exiting of the measurement routine.
In the 'old' routine, the data was successfully logged and retrieved to/from EEPROM.
I don't think the current interrupt version is spending too much time in there (famous last words :) )

Problem I have at the moment is my ICD4 is playing silly buggers, suddenly deciding that it doesn't like the PIC's ID :( Most unreliable programmer. Very temperamental.

Have a PikKit 5 I might try instead tomorrow.

Thanks anyway for your interest and feedback. All of it as always good.

Regards,
Clayton

RGV250

Hi,
Are you using the 16F88 as in the other post?
I have just had another look at the code and I am a bit confused as you are using the hardware serial port pins but software commands. This might be why you are having an issue stopping the measurement with the com port.
Not knowing exactly what you are doing but did the pulse get measured properly before you used the interrupt and not with it? or worked with the interrupt but not when trying to store the time / date.
If it is the latter, can you get the pulse measurement using the interrupt, once you have that set a bit and exit the interrupt. If the pulses are not that fast between then you will now have time to call the save time / date routine etc and clear the bit so it only gets called once before the next pulse.

Bob

Stephen Moss

Quote from: Clayton on Apr 29, 2024, 05:13 PMThe interrupt now fires, the time and date is retrieved, and stored in EEPROM....but the pulse is not measured :(
You indicated in an earlier post that you are using the PulseIn command to try and measure the Pulse width, however from the manual description of the command there are two things to consider here...
  • The clock period used in the PulseIn command appears to be fixed at Fosc/40, therefore depending on the Oscillator frequency, expected range of pulse width and variable size used for storing the the result (Byte/Word) you may want to check the math on that to make sure that...
    • The pulse has not completed before a count can be made (resulting in a count of 0 or 1) or that
    • The pulse width is so long that the variable size for the count would overflow and subsequently return a zero.
  • Second, and perhaps more importantly is that the PulseIn command itself is looking for a change in state on the specified input pin. Therefore if you use IOC on PortB.0 to trigger the interrupt, which then calls the routine using the PulseIn command (as it appears from your posted code), then the edge that the PulseIn command is looking for has already occurred (having been used to trigger the interrupt), consequently unless another pulse occurs within (Fosc/40) * 65535 seconds of the Interrupt executing the PulseIn command then a 0 is returned.

For a beginner new to Positron/PIC programming there is obviously an appeal in using the built in commands such as PulseIn because as it keep things relatively simple for you, that is their purpose.
However, if the interval between pulses in random and likely to be more than (Fosc/40) * 65535 seconds then in this case using PulseIn may not be the best method as your code will spend a lot of time sitting around waiting to measure pulses, and so you may need to consider using a Timer instead, as in something like....
My_ISR:
Context Save
 If INT0IF = 1 Then    'RB0 EXTERNAL Interrupt flag
   
  If TMR1ON = 0 then
      TRM1ON = 1      'Start Timer 1
  Else
    My_Variable = TMR1  'Get Timer Value (Pulse Length)
    TMR1ON = 0          'Stop the Timer
    TMR1 = $0000        'Clear the Timer Value ready to count the next pulse
  EndIF

 INT0IF = 0            'Reset PortB Interrupt Flag
Context Restore        'Exit the ISR

Using a Timer can give you more flexibility as many devices have a choice of both 8 & 16 bit timers, most of which have prescalers and some of which also have postscalers, that combined can allow you to...
1) Obtain a more accurate pulse length than may be available from using the PulseIn command given you chosen Fosc and expected pulse width
2) Get a count in you preferred unit multiples (i.e. 1uS , 10uS, 1mS) and
3) Ensure that both your count value will not overflow the maximum Timer value resulting in an incorrect count, and that your code is not wasting instruction cycles waiting for a pulse input (which may not happen in the relevant time period) to occur.

However, using a Timer this way to count a pulse width is most practical if the device used can be set to trigger an interrupt on both the Rising and Falling Edge simultaneously.
It can be done using devices which only allow you to select one edge or the other to generate the interrupt, but that makes things more difficult as you need to keep changing the IOC edge detection for the IOC to detect both the start and end of the pulse.
    

Clayton

Hi Bob and Stephen,

Thank you very much for your very dilligent and helpful replies.
I thought I had a small grasp of using the Proton environment/language, but have come to realise that I have a way to go :)
Your guidance and comments have gone a long way in educating me (never too old to learn :)).
I must admit that, looking at your suggestions and explanations, I have been doing a very 'knife-and-fork' type of coding, and need to rethink how I approach these things.

True, the attraction of using PulseIn was more aluring than going down the Timer route, but I do see the advantages in using the Timer route and will change to use that. Needless to say I shall make some errors, but that is part of the learning.
To be honest though, PulseIn results, when I have managed to get it to work, were reasonable.

Stephen, after I read your explanation about the triggering using an interrupt, and then starting the PulseIn, it totally made sense that it may not give a reading unless there was another pulse immediately after the first one.

I will go away and re-assess my aproach to this designs flow.

Thanks again gentlemen.
You are a great help and font of knowledge. Excuse my ignorance, but it is diminishing :)

Regards,
Clayton

Pepe

#13
Simulation in Proteus to measure pulse per interruption between 16us and 65ms

Clayton

All,

OK, so it now appears to all be working :)
Thanks Pepe for your proposal. Another one for me to examine to see if I can further improve my program/circuit.

I have to remain most grateful to you all for your kind and thoughtful contributions to my problems.

Hope well for you all.

Thanks and see you here....

Best regards,
Clayton

top204

#15
Most PIC devices have a dedicated peripheral for capturing pulses, named CCP (Capture/Compare/PWM), and the PIC16F88 device has this peripheral. When used with a Timer peripheral in the capture mode, it is very useful for measuring pulse durations with the minimum of fuss.