Incremental encoder with PIC16f877a and interrupts

Discussion in 'Embedded Systems and Microcontrollers' started by AlucardSensei, Aug 9, 2012.

  1. AlucardSensei

    Thread Starter New Member

    Aug 7, 2012
    20
    0
    Hey everyone, I have another problem with this PIC. I'm trying to connect an incremental encoder and make it increase/decrease a value on LCD, and as I've read it, the only way to do that without slippage errors is through interrupts. Now, I've connected everything as needed but the problem is that once I turn the encoder even the slightest bit to one side and start the interrupt routine even once, it simply doesn't stop. Does it have anything to do with the LCD screen connected to the other port where, judging by the board, pins seem to be changing values constantly? (though, if I've understood it correctly, RBIF flag should only respond to changes on PORTB pins 4-7, right?)
     
  2. JDT

    Well-Known Member

    Feb 12, 2009
    658
    85
    Wether you do the encoder in an interrupt or polled in the main loop depends on how fast the encoder changes and how fast your loop can poll the inputs.

    Presumably you are interrupting on change. In the example below I was polling.

    I did this on a PIC a few years ago and so I dug up some of my notes. I started by drawing a state diagram of the encoder sequence (see attached). To guard against spurious miss-counting I identified some "no-change" and error transitions. I then used a computed GOTO in the code to increment or decrement a counter depending on the state of 2 inputs (A,B) previous and current. Hope this helps (and makes sense!).

    Code ( (Unknown Language)):
    1.  
    2. ;Computed GOTO for encoder state diagram.
    3. ;Arrive here with offset in W.
    4. LookUp    ANDLW    H'0F'    ;Mask out high bits now contains A(old), B(old), A(new), B(new)
    5.     ADDWF    PCL,F    ;Add W to the program counter
    6.     GOTO    NoCh    ;0000 No change
    7.     GOTO    Dec    ;0001 Link 5 (decrement)
    8.     GOTO    Inc    ;0010 Link 1 (increment)
    9.     GOTO    NoCh    ;0011 ERROR
    10.     GOTO    Inc    ;0100 Link 4 (increment)
    11.     GOTO    NoCh    ;0101 No change
    12.     GOTO    NoCh    ;0110 ERROR
    13.     GOTO    Dec    ;0111 Link 6 (decrement)
    14.     GOTO    Dec    ;1000 Link 8 (decrement)
    15.     GOTO    NoCh    ;1001 ERROR
    16.     GOTO    NoCh    ;1010 No change
    17.     GOTO    Inc    ;1011 Link 2 (increment)
    18.     GOTO    NoCh    ;1100 ERROR
    19.     GOTO    Inc    ;1101 Link 3 (increment)
    20.     GOTO    Dec    ;1110 Link 7 (decrement)
    21.     GOTO    NoCh    ;1111 No change
    22.  
     
    Last edited: Aug 13, 2012
  3. AlucardSensei

    Thread Starter New Member

    Aug 7, 2012
    20
    0
    If I'm understanding this correctly - what happens if you rotate it clockwise and it goes from 00 to 01, isn't that still a miscount, even though according to your diagram it would decrement the counter?
     
  4. ikalogic

    New Member

    Nov 26, 2004
    3
    0
    I am no PIC expert, but, normally you should only get interrupt on overflow..

    how big is your timer?

    Also, can you start with your timer setup at its half period (32000 if it's a 16 bit timer)? This would prevent underflow interrupt which is probably what you are getting...
     
  5. AlucardSensei

    Thread Starter New Member

    Aug 7, 2012
    20
    0
    No, it's not an overflow interrupt, it's an interrupt on change.
     
  6. John P

    AAC Fanatic!

    Oct 14, 2008
    1,634
    224
    Assuming that you're using the interrupt-on-change feature of Port B, it sounds as if the interrupt is triggering over and over. What are you doing to terminate it? The data sheet says you have to explicitly clear RBIF or read Port B. If you were just testing the pins of Port B one at a time, that wouldn't do it.
     
  7. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,386
    1,605
    If you get stuck inside your ISR then it sounds like you have not reset an interrupt flag. Do you reset the IOC flag? Could there be another interrupt source also firing at the same time?

    Is your LCD code called from the ISR? Generally that is a poor idea (though it can work). It would perhaps be best if you set a value variable inside the ISR, and inside the main loop code check that value against the last displayed value: if they match do nothing, if not, update the display and the last displayed variable.

    Also, port bit change can be troublesome to use if you don't understand the implications of how this feature works: the ISR fires when the port differs from a stored value. ANY read or write to the port saves the value at that time as a new stored value. Thus if you also use part of the change port for other things you can be missing changes. It's a small but still finite probability of happening.

    So if you have anything on Port B 0-3 and access them you may see this problem.

    The work-around for that is to always call the ISR change after any port access. If the port did not change value then nothing happens, if it did (during one of those very rare glitches) you can handle it.
     
  8. AlucardSensei

    Thread Starter New Member

    Aug 7, 2012
    20
    0
    Actually, I've already done something similar to what you've suggested. Here's a piece of the code (which isn't working)

    interrupt routine:
    Code ( (Unknown Language)):
    1. void interrupt irc()
    2. {
    3.     if (RBIF == 1)
    4.     {
    5.         if (DEC_LEFT != enc_bit1_new || DEC_RIGHT != enc_bit2_new)
    6.         {
    7.             RBIF = 0;
    8.  
    9.             enc_bit1_old = enc_bit1_new;
    10.             enc_bit2_old = enc_bit2_new;
    11.             enc_bit1_new = DEC_LEFT;
    12.             enc_bit2_new = DEC_RIGHT;
    13.             change = 1;
    14.         }
    15.         else   
    16.         {
    17.             RBIF = 0;
    18.             change = 0;
    19.         }
    20.     }
    21. }
    part of the main loop:
    Code ( (Unknown Language)):
    1. if ((enc_bit2_old == enc_bit1_new) && change)
    2.         {
    3.  
    4.             if (mode == 0 && vol > 0)
    5.             {
    6.                 vol--;
    7.                 sprintf(vol_char, "%d", vol);
    8.             }
    9.  
    10.             if (mode == 1 && balance > -32)
    11.             {
    12.                 balance--;         
    13.             }
    14.                            
    15.             writeToLcd(mode, vol_char, balance);
    16.         }  
    enc_bit variables are the two bits of the encoder, their old and new values, DEC_LEFT and RIGHT are the pins to which the encoder is connected, specifically RB4 and 5. Basically, I've made it so that if the program detects that the pins have a different value than the stored values (in enc_bit variables), it updates the values and sets the change variable to true, which should set in motion the code above and as soon as you stop turning the encoder, it should set the change to 0 and stop performing the second code. But what happens is that if I so much as flinch while holding the encoder, it perpetually stays in motion to the side I've turned it to. My only idea so far is if maybe interrupts on change can happen on other ports other than port B? I have the LCD screen on port D and its pins seem to be perpetually in motion as well. I have only the RBIE and GIE bits set to 1.
     
  9. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,386
    1,605
    Adjust the ISR code such that:

    if (RBIF == 1)

    you ALWAYS assign

    RBIF = 0;
     
  10. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,386
    1,605
    Code ( (Unknown Language)):
    1.     void interrupt irc()
    2. {
    3.     if (RBIF == 1)
    4.     {
    5. [B]        RBIF = 0;  // HERE  !!!
    6. [/B]        if (DEC_LEFT != enc_bit1_new || DEC_RIGHT != enc_bit2_new)
    7.         {
    8.             RBIF = 0;
    9.  
    10.             enc_bit1_old = enc_bit1_new;
    11.             enc_bit2_old = enc_bit2_new;
    12.             enc_bit1_new = DEC_LEFT;
    13.             enc_bit2_new = DEC_RIGHT;
    14.             change = 1;
    15.         }
    16.         else    
    17.         {
    18. [B]            // RBIF = 0;  NOT HERE[/B]
    19.             change = 0;
    20.         }
    21.     }
    22. }
    23.  
     
  11. AlucardSensei

    Thread Starter New Member

    Aug 7, 2012
    20
    0
    I did that, which solved the problem somewhat, at least it doesn't speed up like crazy now. On further inspection though, it seems that it doesn't detect changes on port B, but on port D instead. :confused:
     
  12. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,386
    1,605
    OK, just noticed you reset that bit in both IF paths... hrmmm....

    I can't tell from your code symbols, do you read PORT B during the ISR? You should do that once and only once, so read it once and save it in a shadow register to test each bit.
     
  13. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,386
    1,605
    Missed your reply... I am going out for the day soon. If you don't get it try posting ALL your code (zip it up and drop it into an attachment).
     
  14. AlucardSensei

    Thread Starter New Member

    Aug 7, 2012
    20
    0
    I have no idea what's wrong, I'm busting my brains for 2 days already to no avail. Here's the code, hope you can find what's wrong with it, but even if not, you've already been so helpful, so thanks for that. :)
     
  15. JohnInTX

    Moderator

    Jun 26, 2012
    2,345
    1,025
    You are not clearing the 'change' variable in main and the code will run in circles as a result. Once ''change' is set, you'll need another IRQonCHANGE to clear it.

    Usually, what you want to do with something like this is set change in the IRQ routine (produce it) and clear it in main when you have consumed it. Its a one way flag (semaphore).

    You are also polling RB0 (DEC_BUTTON) in main. You can't do this when using IRQ on change because every time you do, you'll clear the capture condition. From the datasheet:

    The 18F series is much better in this regard.
    Finally, I've always used some variant of the is-was table posted earlier. You may find that once you get this working, its slow enough to miss encoder revs.
     
  16. AlucardSensei

    Thread Starter New Member

    Aug 7, 2012
    20
    0
    Yes, how silly of me, how was it ever going to stop if it was never supposed to enter the interrupt routine again. And I call myself a programmer. :D In my defense, the entire code was in the main loop first, and I just copied it without thinking when I started fiddling with interrupts. Thanks a bunch, that solved the issue. :)
     
  17. JohnInTX

    Moderator

    Jun 26, 2012
    2,345
    1,025
    Your enc_bitx_xxx variables are ints, presumably 16 bits?

    If that's the case, you'll need to disable RBIE while you are testing the values. That's because each byte is tested separately. Its possible that halfway through the test you'll get an IRQ that modifies the 16 bit value in the middle of testing it. These things are hard to troubleshoot.

    In main, disable the IRQ, copy the values to temp registers, reenable IRQs, use only the value in the temp regs. Using temp regs also minimizes the time that the IRQ is off.

    Good luck!
     
  18. AlucardSensei

    Thread Starter New Member

    Aug 7, 2012
    20
    0
    Yeah, thanks a lot, all of your tips have been quite helpful. I also got one more question, I think I've seen this done, but I'm not too sure - is there a way to write "inverse" characters, as in, dark background and light characters?
     
  19. JohnInTX

    Moderator

    Jun 26, 2012
    2,345
    1,025
    .. its done by re-orienting the polarizers on the LCD display. If you have one to mess with, carefully peel the top polarizer (plastic film) from the LCD glass and rotate it 90deg. Presto. Light chars on dark background. You can usually order the LCD modules this way..

    Other than that, I don't recall if the Hitachi LCD controllers have a reverse text function. Even so, all you would have is black character boxes with light chars inside. Ugly.
     
  20. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,386
    1,605
    I think you can only do that with the character at the cursor position. It would be great if you could do it at will anywhere. I used a B&W graphic LCD once and simple reverse dots looked good and worked well as a highlight.
     
Loading...