Connect with us

Interrupt Do's and Don'ts

Discussion in 'Microcontrollers, Programming and IoT' started by jpanhalt, Mar 25, 2014.

  1. jpanhalt

    jpanhalt

    426
    4
    Nov 12, 2013
    I have read in multiple places that one should not use a delay or polling within an interrupt.

    Aside from the fact that during a delay, the MCU might do something else useful, why is a short delay or polling within an interrupt a bad idea?

    For a specific example, I am using the EUSART of a 16F1519 chip to receive two pairs of 8-bit bytes (i.e., two, 16-bit counts). I am polling the RCIF flag to detect the second 8-bit byte of each pair. Language = MPASM

    That part of the program is working well and seems robust.

    John
     
  2. Harald Kapp

    Harald Kapp Moderator Moderator

    9,136
    1,844
    Nov 17, 2011
    John,
    doing so may work - it obviously does in your case - but is mostly no good practice. It may be necessary, however, in some cases, see below.

    An interrupt is meant to interrupt :D the main program in whatever it is doing to immediately (or at least very quickly) serve some purpose that doesn't allow for long waiting time, e.g. fetching data from some communication port in order not to lose communication only because the CPU was too busy to notice the arrival of new data. A typical interrupt service routine would stow away the data, possibly set a flag to notify the main program of the arrival of new data, then return from the service routine.

    You want to keep the interruption short because:
    - the CPU may have other important things to do that should not wait too long
    - another interrupt may occur that needs to be serviced

    While you can have an interrupt interrupt an interrupt (pun intended) - that's called nested interrupts - you should allow so only for interrupts with higher priority than the one currently being serviced.
    If you start polling within an interrupt service routine, you'r probably wasting CPU time. Polling is rather ineffective, especially if you poll within a loop until the expected event has occurred. During this time the CPU could have executed lots of possibly much more usefull instructions other than waiting for an event (e.g. change of a flag).

    If your CPU has some spare time to burn, nothing is lost by polling within the interrupt. There may even be cases, where polling within an interrupt service routine may be required, e.g. if a first event can trigger the interrupt, but a second event can't trigger an interrupt but requires to be polled.
    If you know the timing, you could, however, set up a timer interrupt within the first interrupt service routine that calls another service routine after a specified time to poll the second event. If the event has not yet occurred, restart the timer.

    In practice you need to consider the timing requirements of your program, available headroom for polling, the kind of event source (w/o interrupt) etc.


    Mind that I'm more of a hardware guy - an experienced programmer may have a different view.

    Harald
     
    Last edited: Mar 25, 2014
  3. kpatz

    kpatz

    247
    52
    Feb 24, 2014
    Your way works but it's inefficient, in that when the interrupt is polling RCIF to wait for that 2nd byte, no other code is running.

    A better way is to use a state variable and an interrupt. When a byte comes in over the UART, the interrupt occurs and your interrupt service routine would grab the byte and based on the state variable, store it in the appropriate place and then increment the state variable, then return. Then, your main loop can execute in between receipt of each byte.

    In the ISR, you'd check the state variable such as this: if it's 0, you're receiving the 1st byte of the 1st count. If it's 1, you're receiving the 2nd byte of the first count. If it's 2, you're receiving the 1st byte of the 2nd count. If it's 3, you're receiving the 2nd byte of the 2nd count, and at that point you'd clear the state variable back to 0. To make the assembler code smaller, I check bits 1 and 0 of the state variable individually in my code sample instead of comparing with 0, 1, 2, and 3. If you were using C you could use a switch() statement.

    Something like this. Note I didn't include variable declarations or banksel macros, nor have I tested this. There could be errors.

    Code:
    ; intstate: 0=count1_byte0, 1=count1_byte1, 2=count2_byte0, 3=count2_byte1
    
    isr:   btfss pir1, rcif   ; Check if receive data
           goto  next_int   ; Branch to next interrupt check if not
           movf  RCREG,w  ; get byte from UART (this clears RCIF on most PICs)
           btfsc intstate, 1 ; Check if intstate is 2 or 3
           goto  count2
    ;  count 1 byte received
           btfsc intstate, 0 ; Check if intstate bit 0 is 1
           movwf count1_byte1  ; store in count 1 byte 1
           btfss intstate, 0 ; Check if intstate bit 0 is 0
           movwf count1_byte0  ; store in count 1 byte 0
           goto  end_rcif
    count2:
    ;  count 2 byte received
           btfsc intstate, 0 ; Check if intstate bit 0 is 1
           movwf count2_byte1  ; store in count 2 byte 1
           btfss intstate, 0 ; Check if intstate bit 0 is 0
           movwf count2_byte0  ; store in count 2 byte 0
    end_rcif:
           incf   intstate, f      ; increment intstate
           movlw 0x3            ; mask out all but bottom 2 bits (not needed here but we'll do it anyway)
           andwf  intstate,f
    next_int:
           ; Check next interrupt here if using other interrupts
           retfie
    
     
  4. jpanhalt

    jpanhalt

    426
    4
    Nov 12, 2013
    Harald,

    Thanks for the response.

    I was worried the recommendation was based on some subtle problem, other than wasted time. That level of chip does not support nested interrupts.

    John
     
  5. jpanhalt

    jpanhalt

    426
    4
    Nov 12, 2013
    @kpatz

    I did something like that to distinguish the two, 16-bit counts. I used bits in a flag, "GIEF," to keep the two, 8-bit bytes together properly. I do return from the interrupt between the first pair of bytes and the second (GIE is global interrupt enable).

    The signal that is being measure is only 100 Hz at 40% to 60% duty cycle (10,000 counts total), so there is a lot of time there.

    John
     
  6. gorgon

    gorgon

    603
    23
    Jun 6, 2011
    I just wonder why you use interrupt in the first place if your program allow this kind of wasted overhead. Relative to the communication time, you waste 50% of your cpu's capacity in this period(one byte interrupted and one byte polled). Depending on the communication speed, thise 'blips' in processing capacity may come and haunt you in a later phase of your development.

    From a professional point of view, I would have used a x times 2 byte input buffer, and a semaphore in the interrupt routine that kept the phase of hi and low byte in the 16 bit value, and another semaphore signalling a complete received value. The buffer would need room for at least 2 x 16 bits values, so you don't mix partly received and complete values. From what you say, I assume you are not transmitting anything in this communication. Depending on The UART in question and the number of interrupt vectors, that could be fun in this setting.
     
  7. jpanhalt

    jpanhalt

    426
    4
    Nov 12, 2013
    You are absolutely right.

    I am a relative newbie at programming and had not used interrupts before. This was a simple project in which I could implement one. In fact, I use a gated period during which I enable GIE. That is so I can be sure where it occurs and capture a full cycle. I had been thinking of eliminating even that and using a poll. An interrupt is really not necessary.

    I am retired and have a small farm that keeps me more than busy during three seasons plus. This project is entering its third year of 2-month stints, and it is hard to put into words how much fun it has been. While interrupts are a nice tool and perhaps are essential in the hands of programmers who need utmost efficiency. For someone at my stage, it is just nice to know how they work.

    Somewhat related... several months ago, I asked on another forum why one couldn't just use a "goto" instead of a "retfie" (with appropriate adjustments, of course). I was not completely happy with the answer.

    In this case, my goal is simply to display via a wireless link (XBee) the inclination of pallet forks on a front-end loader of a tractor. That was accomplished three years ago using 4-wire telephone cable, but it was not very challenging to do. There have been a lot of turns and re-learning in this project, but I am finally getting to the point of having something I can tape to the dashboard to tell me when the forks are level on a GLCD. I had everything working an a 2X16 LCD last year. I am just doing clean-up.

    One problem with datasheets and tutorials, is that one cannot ask questions about good practices and "why," as one might do in a seminar. These forums are a substitute.

    I do appreciate your feedback.

    John

    PS: I don't use a semaphore. I use a sort routine, since for my purpose, it doesn't matter whether the 16-bit high period is captured first or last.
     
    Last edited: Mar 26, 2014
  8. Harald Kapp

    Harald Kapp Moderator Moderator

    9,136
    1,844
    Nov 17, 2011
    "Goto" requires taht you know where to jump. In the case of an interrupt service routine, you don't know where you came from. Your main program could have been doing anything (any address in program counter). When the interrupt is serviced, the cpu saves the address (prgram counter) of the next instruction that would have been executed in teh main routine, then jumps to the interrupt service routine.
    "Retfie" fetches that stored address and puts it in the program counter, so the main routine can continue where it was interrupted. In addition, "retfie" sets the GIE bit thereby enabling interrupts again.

    By the way: you are aware that the service routine needs to do some housekeeping, are you? Any registers used by the interrupt service routne should be saved before use and restored before "retfie". Otherwise unpredictable behavior of the program will result since the main routine is not aware of changes made to registers by the interrupt service routine and will continue to work with possibly wrong register contents.
     
  9. gorgon

    gorgon

    603
    23
    Jun 6, 2011
    Many microcontrollers only save the PC (program counter) when entering the interrupt routine, and the first register you need to save is the flag register, and let it be saved unchanged. Depending on the instruction set you may need to push the accumulator before pushing a copy of the flag register. Push and pop instructions will not change the flagregister.
    The next important thing to remember is that you need to do this in the exact reversed order when leaving the interrupt routine.
     
  10. jpanhalt

    jpanhalt

    426
    4
    Nov 12, 2013
    At the time when I questioned using a GOTO from an interrupt, I was using the 12F683, which does not allow direct access to the stack and has neither a RESET nor automatic context saving, such as the 16F1519 has. I was using the GOTO basically as a Reset following an IOC.

    That concern became a non-issue when I decided not to use an interrupt.

    As an FYI, a similar question was asked by another person on the MIcrochip forum in 2006 and received this response:
    I am aware of the housekeeping need. Automatic context saving by the enhanced mid-range chips simplifies that process.

    My day will now be wasted going to Cleveland for some housekeeping of the physical type.

    Regards, John
     
Ask a Question
Want to reply to this thread or ask your own question?
You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.
Electronics Point Logo
Continue to site
Quote of the day

-