Incremental encoder with PIC16f877a and interrupts

Thread Starter

AlucardSensei

Joined Aug 7, 2012
20
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?)
 

JDT

Joined Feb 12, 2009
657
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!).

Rich (BB code):
;Computed GOTO for encoder state diagram.
;Arrive here with offset in W.
LookUp    ANDLW    H'0F'    ;Mask out high bits now contains A(old), B(old), A(new), B(new)
    ADDWF    PCL,F    ;Add W to the program counter
    GOTO    NoCh    ;0000 No change
    GOTO    Dec    ;0001 Link 5 (decrement)
    GOTO    Inc    ;0010 Link 1 (increment)
    GOTO    NoCh    ;0011 ERROR
    GOTO    Inc    ;0100 Link 4 (increment)
    GOTO    NoCh    ;0101 No change
    GOTO    NoCh    ;0110 ERROR
    GOTO    Dec    ;0111 Link 6 (decrement)
    GOTO    Dec    ;1000 Link 8 (decrement)
    GOTO    NoCh    ;1001 ERROR
    GOTO    NoCh    ;1010 No change
    GOTO    Inc    ;1011 Link 2 (increment)
    GOTO    NoCh    ;1100 ERROR
    GOTO    Inc    ;1101 Link 3 (increment)
    GOTO    Dec    ;1110 Link 7 (decrement)
    GOTO    NoCh    ;1111 No change
 

Attachments

Last edited:

Thread Starter

AlucardSensei

Joined Aug 7, 2012
20
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?
 

ikalogic

Joined Nov 26, 2004
3
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...
 

John P

Joined Oct 14, 2008
2,026
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.
 

ErnieM

Joined Apr 24, 2011
8,377
...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.
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?

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?)
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.
 

Thread Starter

AlucardSensei

Joined Aug 7, 2012
20
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:
Rich (BB code):
void interrupt irc()
{
	if (RBIF == 1)
	{
		if (DEC_LEFT != enc_bit1_new || DEC_RIGHT != enc_bit2_new)
		{
			RBIF = 0;

			enc_bit1_old = enc_bit1_new;
			enc_bit2_old = enc_bit2_new;
			enc_bit1_new = DEC_LEFT;
			enc_bit2_new = DEC_RIGHT;
			change = 1;
		}
		else	
		{
			RBIF = 0;
			change = 0;
		}
	}
}
part of the main loop:
Rich (BB code):
if ((enc_bit2_old == enc_bit1_new) && change)
		{

			if (mode == 0 && vol > 0)
			{
				vol--;
				sprintf(vol_char, "%d", vol);
			}

			if (mode == 1 && balance > -32)
			{
				balance--;			
			}
							
			writeToLcd(mode, vol_char, balance);
		}
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.
 

ErnieM

Joined Apr 24, 2011
8,377
Rich (BB code):
    void interrupt irc()
{
    if (RBIF == 1)
    {
        RBIF = 0;  // HERE  !!!
        if (DEC_LEFT != enc_bit1_new || DEC_RIGHT != enc_bit2_new)
        {
            RBIF = 0;

            enc_bit1_old = enc_bit1_new;
            enc_bit2_old = enc_bit2_new;
            enc_bit1_new = DEC_LEFT;
            enc_bit2_new = DEC_RIGHT;
            change = 1;
        }
        else    
        {
            // RBIF = 0;  NOT HERE
            change = 0;
        }
    }
}
 

Thread Starter

AlucardSensei

Joined Aug 7, 2012
20
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:
 

ErnieM

Joined Apr 24, 2011
8,377
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.
 

ErnieM

Joined Apr 24, 2011
8,377
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).
 

Thread Starter

AlucardSensei

Joined Aug 7, 2012
20
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. :)
 

Attachments

JohnInTX

Joined Jun 26, 2012
4,787
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 user, in the Interrupt Service Routine, can clear the
interrupt in the following manner:

a) Any read or write of PORTB. This will end the
mismatch condition.
b) Clear flag bit RBIF.

A mismatch condition will continue to set flag bit RBIF.
Reading PORTB will end the mismatch condition and
allow flag bit RBIF to be cleared.

The interrupt-on-change feature is recommended for
wake-up on key depression operation and operations
where PORTB is only used for the interrupt-on-change
feature. Polling of PORTB is not recommended while
using the interrupt-on-change feature
.
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.
 

Thread Starter

AlucardSensei

Joined Aug 7, 2012
20
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. :)
 

JohnInTX

Joined Jun 26, 2012
4,787
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!
 

Thread Starter

AlucardSensei

Joined Aug 7, 2012
20
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?
 

JohnInTX

Joined Jun 26, 2012
4,787
.. 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.
 

ErnieM

Joined Apr 24, 2011
8,377
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?
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.
 
Top