Interrupt on change high to low on Pic

Discussion in 'Embedded Systems and Microcontrollers' started by spinnaker, Oct 16, 2015.

  1. spinnaker

    Thread Starter AAC Fanatic!

    Oct 29, 2009
    4,866
    989
    I am enabling interrupt on change on a Pic18F26K22. The interrupt is being triggered all of the time because the normal state is high. Is there any way to change the configuration of the Pic so the interrupt triggers when going low?
     
  2. joeyd999

    AAC Fanatic!

    Jun 6, 2011
    2,675
    2,723
    Interrupt on change is just that. It interrupts when any of the port b7:4 pins change to a different value. It is cleared by reading the port.

    If you want an interrupt only for a falling edge, use one of the external interrupts.
     
  3. JohnInTX

    Moderator

    Jun 26, 2012
    2,341
    1,024
    I think @joeyd999 has nailed it but you also should know that you have to read the port to clear any mismatch before counting on the interrupt. You also have to read the port after any int-on-change to prepare for the next one, potentially losing changes that happen between the first one and the time you service the interrupt. IOC can be incredibly useful but only in certain cases.
     
  4. spinnaker

    Thread Starter AAC Fanatic!

    Oct 29, 2009
    4,866
    989
    That was it! Thanks! Makes sense now I think of it. Code didn't yet get to the point where I was reading the port. Just wanted to get the interrupt to work.
     
  5. spinnaker

    Thread Starter AAC Fanatic!

    Oct 29, 2009
    4,866
    989
    What I am looking to use this as a home sensor for a propeller clock. The sensor is an infrared emitter/ detector pair. I plan to mount the clock on a PC fan.

    Any issues I need to be aware there?
     
  6. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,386
    1,605
    Yeah, about a ton. Interrupt on change was one of the least thought out features ever added to any commercially produced device. However, since it basically belongs to those who already have it built into existing devices it will never be fixed.

    You can trip it by writing to a port. You can mask a trip when reading from a port. Do go to the Microchip site and learn the problems one can have.
     
  7. nsaspook

    AAC Fanatic!

    Aug 27, 2009
    2,907
    2,165
    Yea, it's got a timing bug (in some chips) that can cause problems with fast signals.
    http://www.microchip.com/forums/m294169.aspx
    http://www.xargs.com/pic/portb-change-bug.html

    PIC18(L)F2X/4XK22
     
    Last edited: Oct 17, 2015
  8. spinnaker

    Thread Starter AAC Fanatic!

    Oct 29, 2009
    4,866
    989
    So if I read this correctly, a read of one of the other pins on Port B might screw me up? Pure dumb luck but I am not currently using them for anything right now. RB6 and RB7 are also PGC and PGD. I usually like trying to avoid using those pins in the design.
     
  9. nsaspook

    AAC Fanatic!

    Aug 27, 2009
    2,907
    2,165
    If you know what to expect you can code around it. I've used IOC with two rotary quadrature encoder A/B inputs (low speed HID control knob interfaces) without any problems. For a simple home/index sensor the external interrupt pin setup might work better.
     
  10. spinnaker

    Thread Starter AAC Fanatic!

    Oct 29, 2009
    4,866
    989
    OK very strange. When the transition is from low to high, the IOC occurs almost instantaneously but if the transition is high to low, it can take at least 2 seconds for the interrupt to occur. Any ideas as to why?

    There is nothing going on at the same time except a while(1) loop.
     
  11. joeyd999

    AAC Fanatic!

    Jun 6, 2011
    2,675
    2,723
    I've done the same for 1, but it could be easily expanded to two.
     
  12. spinnaker

    Thread Starter AAC Fanatic!

    Oct 29, 2009
    4,866
    989
    Any idea what would cause that delay in interrupt from low to high?

    So I decided to try and invert the output from the sensor. Now I am not getting an interrupt at all though I do see a change in value on the pic. Why would the interrupt not trigger?

    Here is the schematic

    upload_2015-10-18_14-44-10.png

    Here is the updated code:

    Code (Text):
    1.  
    2.  
    3.  
    4. #define HOME_SENSOR_INTERRUPT_ENABLE()  INTCONbits.RBIE = 1
    5.  
    6. void interrupts_Init()
    7. {  
    8.   RCONbits.IPEN = 1;     // Enable priority levels on interrupts
    9.   INTCONbits.RBIE = 1;  // Interrupt on change
    10.   IOCBbits.IOCB4 = 1;  
    11.   INTCONbits.GIEH = 1;
    12.   INTCON2bits.INTEDG0 = 0;
    13.   INTCONbits.RBIF = 0;
    14.    
    15.   HOME_SENSOR_INTERRUPT_ENABLE();  
    16.  
    17. }
    18.  
    19.  
    20. void interrupt high_priority interrupts_highPriority(void)
    21. {
    22.   // uart_putStringRom("High Priority Interrupt\r\n\r\n");
    23.  
    24.   if ( INTCONbits.RBIF == 1)
    25.   {
    26.   int x=HOME_SENSOR_RX_PIN;
    27.   if (HOME_SENSOR_RX_PIN)
    28.   LATA = 1;
    29.   else
    30.   LATA = 0;
    31.  
    32.   INTCONbits.RBIE = 1;  // Interrupt on change
    33.   //IOCBbits.IOCB4 = 1;
    34.   INTCONbits.RBIF = 0;
    35.   }
    36.  
    37. }
    38.  
     
  13. JohnInTX

    Moderator

    Jun 26, 2012
    2,341
    1,024
    Pretty sure that RB0 is not an interrupt on change - only RB7-4 are IOC. That means that you are enabling the wrong interrupt (RBIE/IF is for interrupt on change, not RB0 interrupt). RB0 is INT0 so you need to enable INT0IE in INTCON and manage INT0IF, also in INTCON.

    I would also take care not to enable the interrupt until its fully configured and INT0IF has been cleared to take care of power up strays. Lines 9 and 11 enable interrupts before configuring the edge.

    I would do something like this for interrupt on RB0:
    Code (C):
    1.  
    2. void interrupts_Init()
    3. {
    4.   INTCON = 0;            // just disable everything for init then..
    5.  
    6. // remove IOC stuff, you are on RB0 (INT0)
    7.  
    8.   RCONbits.IPEN = 1;         // Enable priority levels on interrupts
    9.   INTCON2bits.INTEDG0 = 0;    // interrupt on falling edge
    10.   INTCONbits.INT0IF = 0;    // clear the interrupt flag
    11.   INTCONbits.INT0IE = 1;   // enable the interrupt
    12.   INTCONbits.GIEH = 1;        // enable hi priority interrupts
    13. }
    14.  
    15. void interrupt high_priority interrupts_highPriority(void)
    16. {
    17.   // uart_putStringRom("High Priority Interrupt\r\n\r\n");
    18.   if ( INTCONbits.INT0IF == 1)
    19.   {
    20.    int x=HOME_SENSOR_RX_PIN;
    21.    if (HOME_SENSOR_RX_PIN)
    22.     LATA = 1;
    23.    else
    24.     LATA = 0;
    25.   INTCONbits.INT0IF = 0;      // clear interrupt flag
    26.   } // INT0 service
    27.  
    28.   else{
    29.     INTCON = 0;            // stray interrupt! kill all
    30.     uart_putStringRom("Unknown Interrupt!!!!!\r\n\r\n");
    31.   }// bad IRQ
    32. } // HiPrio IRQ handler
    IIRC, you can use the ext interrupts in the legacy (non priority) mode. Its just that if you elect to use priority IRQs, INT0 (RB0) is always a high priority.
    Hope that helps!
     
  14. spinnaker

    Thread Starter AAC Fanatic!

    Oct 29, 2009
    4,866
    989
    Ahhhhhhh! I do not know where I read that RB0 had IOC. I just reread the datasheet and you are 100% correct. Only R7- RB4.

    The problem is I started this project long ago and am picking it up again. I should have reconfirmed the docs.

    I have RB7-RB4 available should I just move to that?


    I am a little confused about your RB0 fix. If Rb0 is not IOC, how can it generate an interrupt?


     
  15. JohnInTX

    Moderator

    Jun 26, 2012
    2,341
    1,024
    Ahh.. OK. 'Interrupt On Change' in the PIC context is a special kind of interrupt. It allows you to monitor up to 4 pins at once for a change in either direction. As @nsaspook noted, its good for things like decoding a quadrature encoder where you have more than one phase doing things.

    Of course! RB0 must change also (its edge triggered) but that's taken for granted and not called InterruptOnChange. That term is reserved for the special case using Rb7-RB4. As you were finding out, using these for a GP interrupt is problematic because of the restrictions on reading the port, having interrupt on both edges etc.

    If you are just wanting to interrupt on a single line, you can use one of the external interrupts as I've shown. If you want to interrupt on both edges you can toggle the edge select in the interrupt service routine before clearing the flag. Note that the external int (INT0) does not have the issues with portB reads/writes that interrupt on change does.

    You still can use IOC on RB7-4 but it will take a bit more care than just using one of the external interrupts.

    Howzzat?
     
  16. spinnaker

    Thread Starter AAC Fanatic!

    Oct 29, 2009
    4,866
    989
    So RB0 is just a plain old external interrupt and not called interrupt on change even though it interrupts when the input changes? ;)

    I am still confused by "It allows you to monitor up to 4 pins at once for a change in either direction". What do y mean by "change in either direction"? It can also interrupt when the pin is an output?


    OK this is for a home sensor for an external clock. What would be the best way to go?
     
  17. JohnInTX

    Moderator

    Jun 26, 2012
    2,341
    1,024
    The best example I can think of is the quadrature encoder - lets use a rotary knob with the two phases A and B. As you rotate the switch in one direction, the two phases will run 90deg out with respect to each other with one leading the other. Turn it the other way and both phases also run but now the other one leads. Each click of the knob changes a one of the phases. To fully read this, you need to know about each click so you have to monitor both phases and interrupt when either changes. The interrupt service routine reads both inputs, compares them with the previous value and knows which way the knob turned. But to do this, you need an interrupt on a change of either phase. IOC on portB is a convenient tool to do that. But it comes with caveats - the change detection logic resets each time you read the port so care has to be taken there. uCHIP recommends against polling portB when IOC is used. But that's it in a nutshell, a single interrupt when any line on RB7-4 changes in any way. The service routine sorts it out.

    Depends on the PIC. Later/better PICs have more tolerance here - outputs on the IOC pins are ignored. This PIC allows you to select individual pins in RB7-4 to be a part of IOC. I haven't read carefully enough to see what other caveats there may be - I don't usually mess around with it except for special cases and then carefully.

    A single input like yours is more suited to one of the external interrupt lines. These don't have any of the restrictions that IOC on RB7-4 have. Just set it up and wait until something happens, process it then carry on.

    You might consider just polling the input if your code is such that it can get around to it often enough. Simpler yet.

    A general, combined approach is to use the external interrupt to log that the event (your detector) has triggered, and just post a flag to that effect. The main routine can then process it at its leisure without worry about missing a quick signal. After the main routine processes the detector flag, it clears it and waits for the next time its posted (by the interrupt routine again). There can be variations on this producer->consumer approach but that's the basic idea. If main is slow but you want to know how many times the detector clicked, the flag becomes a counter - incremented by the interrupt and decremented by main as it processes the events.

    Lots of ways to skin it but along those lines has worked for me.

    EDIT:
    Thinking more, I would have the main routine generate a buffer full of the patterns to display. For example, if you were swinging a row of 8 LEDs that blinked out the time digits as it swept the circle main would keep time, decode the time into sweepable digit patterns composed of successive 'columns' of LEDs and post them in a pattern array. MAIN is the producer here.
    The sole task of the interrupt routine is 'consumer' - to detect the 'home' sensor then spit out the simple patterns at intervals until the end of the buffer. Then it sets up to sync to the next 'home' signal.
    The rotation speed vs. the processor speed may have some effect on the timing. All things being up to snuff, I would consider this way:
    The home sensor interrupt gets tripped. It enables a pre-setup timer interrupt that spits out the predetermined LED columns at some rapid interval. When it reaches the end of the array of LED patterns, it disables the timer interrupt and the whole thing waits for the 'home' sensor. The timer interval would be computed by MAIN after figuring out how fast the thing was rotating (from the period of the 'home' interrupts.)

    Meanwhile MAIN, when not in the interrupt routine, goes about its business keeping time and updating the array of swept LED column patterns.

    If the rotation speed is fast enough and the PIC is slow enough that it doesn't make sense to use a timer interrupt to spit out the columns, that could be done right after the 'home' interrupt with a simple delay between the columns. That would mean that MAIN processing would be in spurts (when the rotor was done displaying). I prefer the former approach. Speed up the PIC if you have to.

    Cool!
     
    Last edited: Oct 18, 2015
  18. spinnaker

    Thread Starter AAC Fanatic!

    Oct 29, 2009
    4,866
    989
    Yep already had it in mind to do what u propose. Some sort of buffer for the display.

    I am planning on using a PC fan motor. Would I display one byte of the buffer per rotation? It would be a lot easier. Or would that not be fast enough for persistence of vision?
     
  19. JohnInTX

    Moderator

    Jun 26, 2012
    2,341
    1,024
    I'd do the spitting out of all of the patterns in each rotation. Besides POV issues you'd have to be pretty spiffy to get each column of LED patterns indexed one turn plus one column for each rotation.
    Figure out the time you have in the visible arc of one sweep and see if it looks like the PIC can keep up. I would expect it can but that's why you make MAIN do the heavy lifting of keeping time, decoding, loading patterns etc. to lessen the computational load on the rotation-synced IRQ pattern spitter. If pushed for time, stay in the IRQ routine that 'home' triggered and spit out the all the LED columns in one shot. That way, you don't have to worry about interrupt overhead for each column. I would try the timer-generated column TIK with the fast PIC, but that's the backup plan.

    It'll work.

    EDIT: You will likely find that indexing an array ( segPattern) is faster than using pointers. That's my experience at least with HiTechC and early XC8. Look at the dis assembler output or list file to be sure.

    Consider pre-fetching the next LED column value i.e. rather than getting the timer interrupt, going to the array and taking up all of that time when the column needs to be displayed NOW, prefetch the NEXT column to be displayed and put it into a quick (near) byte. Then when the timer interrupts just grab that byte and write it to the port (consider #asm and MOVFF) immediately before doing anything else THEN fetch the next pattern, put it into the near buffer, deal with counts etc. On the next timer interrupt, the byte you pre-fetched will be output without delay and so on..
     
    Last edited: Oct 18, 2015
  20. spinnaker

    Thread Starter AAC Fanatic!

    Oct 29, 2009
    4,866
    989
    The problem I see doing them all in one turn is I will need two timers or at least two timer intervals. The first time to display the first column then a delay between each column after that. I suppose I could just use a standard delay for each of those ???
     
Loading...