Using a rotary encoder switch with an AVR

Go To Last Post
6 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello,
I read through zbaird's tutorial about Using a rotary encoder switch with an AVR . I am having bit to trouble making it work.

I use Atmega32A with 11.0592 Meg Crystal
My portB and portC 4...7 are for displaying on 4 muxed 7-segment displays

My PORTD is input port for rotary encoder, I connected line A to PD2 and Line B to PD0.

I need to increment or decrement variable num.

but I think when it reaches ISR for INT0 and try to determine value of line-B, it turns out to be always 0 thus it increments only in one direction. What could have gone wrong in following code?

btw I removed these encoder from scroll of an old mouse, with .1uf caps (104) between com & A and com & B, for hardware debounce. Right now I turn the encoder by hand but eventually it will be turned by a geared motor at 300 to 600 rpm i.e. the frequency of changes in encoder will be 60Hz to 120Hz. How does the speed of rotation effect the precision?

I eventually want to use these as feedback for my robot's PWM tunning via PID to make the robot go straight.

Code:

#define F_CPU 11059200UL

#include 
#include 
#include 
#include 
#include 

#define CHECKBIT(x,y) (x & (y)) /* Check bit y in byte x*/
#define SETBIT(x,y) (x |= (y)) /* Set bit y in byte x*/
#define CLEARBIT(x,y) (x &= (~y)) /* Clear bit y in byte x*/

//lookup table ascii char to 7 segment common anode type
static const uint8_t PROGMEM sevsegascii_table[] = {
   0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8,   0x80, 0x90, 0xbf // 0 1 2 3 4 5 6 7 8 9 -
};

void displayNumber(int number);
void printNumber(int number);
volatile int num;
volatile int ones;
volatile int tens;
volatile int hundreds;
volatile int thousands;

int main(void)
{
   DDRB = 0xff;
   PORTB = 0x00;
   DDRD = 0x00;   //input port
   PORTD = 0xFF;  //enable pull up resistor
   DDRC = 0xff;
   PORTC = 0x10;
   
   TCCR0 |= (1<< CS02);   //256 PRESCALER
   TCCR0 |= (1<< WGM01);   //CTC MODE
   OCR0 = 65;   //4ms //setting how fast to refresh the display

   // read into initial value to set rising or falling edge
   if (PIND & (1<<PD0)) MCUCR = 0x02;         //int0 interrupt on falling edge
   else  MCUCR = 0x03;                        //int0 interrupt on raising edge
   //MCUCR = 0x01;
   
   GICR  = 0x40;         //enable the int0 interrupt
   TIMSK |= (1<<OCIE0);   //enable the timer0 compare interrupt
   sei();               //enable global interrupts

   num = 10;   //initializing the "counter"

   while(1)
   {
         printNumber(num);
   }
   return 0;
}
void printNumber(int number)
{
   int temp = 0;
   //splitting the number into separate numbers
   //to display on the 4 digit 7-segment display
   thousands = floor(number / 1000);
   temp = number % 1000;
   hundreds = floor(temp/100);
   temp = number % 100;
   tens = floor(temp/10);
   ones = temp % 10;
}
//displaying the number on the 7 segment display
void displayNumber(int number)
{
   PORTB = pgm_read_byte(&sevsegascii_table[number]);
}

ISR(TIMER0_COMP_vect)
{

   //selecting which digit to display
   //timing controlled by Timer0
   switch(PORTC)
   {
      case 0x80:
         displayNumber(hundreds);
         PORTC = 0x40;
         break;
      case 0x40:
         displayNumber(tens);
         PORTC = 0x20;
         break;
      case 0x20:
         displayNumber(ones);
         PORTC = 0x10;
         break;
      case 0x10:
         displayNumber(thousands);
         PORTC = 0x80;
         break;
      default:
         break;

   }
}

ISR(INT0_vect)
{
   if ((PIND & (1<<PD0))) {
      num--;
      MCUCR = 0x03;
   }      
   if (!(PIND & (1<<PD0))) {
      num++;
      MCUCR = 0x02;
   }

}

Thanks,
K

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

1) If you intend to use for motor work, I'd be sure that the mechanics of the encoder wheel will stand up to that type of use and total cycles. A "wiper type" may not give satisfactory lifetime.

2) Going through the paper quickly, note that Chuck talks about >>four<< states, A/B/C/D. Your code only has two states. That may well be the root of your situation.

3) Small point--your code does
if ((PIND & (1<<PD0))) {
...
if (!(PIND & (1<<PD0))) {

The state of that pin is going to be one or the other. >>just in case<< the secondary pin is near a transition point when the interrupt is being serviced, I'd do

if ((PIND & (1<<PD0))) {
...
else {

4) I've done a number of production encoder apps, and I've never fussed with edge selection in the ISR. As that approach is x2 quadrature, both edges of one channel, then I just do that. If the primary channel is high in the ISR after an edge trigger, then it was a rising edge. If not, it was a falling edge. So that is one check on the primary channel. The switching logic will take more cycles and code.

4a) Note that switching edges is done, on that model, between 0x02 and 0x03. So instead of conditional logic, one could do

MCUCR ^= 1;

to toggle the low bit of that register.

If you want to see the resulting ISR with both edges enabled look at
https://www.avrfreaks.net/index.p... and the rest of the thread. There is a bit in there for the "saturation", but you can see there isn't much to the ISR. And no edge switching. ;) You see, though, that there are four cases being handled just as in Chuck's paper.

See my post near the top of https://www.avrfreaks.net/index.p... . IMO my approach is cleaner (and faster--important for high edge rates) than those that use "previous" or those that re-program edge selection or those that have a switch().

That said, 'Freak "danni" is the master and does it: https://www.avrfreaks.net/index.p... For my industrial encoder apps, we ensure clean edges so no debounce. (BTW, you might want a Schmitt-trigger in your signal conditioning.)

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just for fun, let's look at one of my "interrupt on both edges, and look at the signal to determine which edge" ISRs, and then compare to the edge-switching approach.

// **************************************************************************
// *
// *		P I N _ C H A N G E _ I S R 2
// *
// **************************************************************************
//

// Pin change 16-23 interrupt service routine
//	Used for a pin change interrupt on PCINT18 (PIND.2) for recognizing
//	encoder pulses (Hall switch channel A).
interrupt [HALLA_INT] void pin_change_isr2(void)
{
// An edge on HALLA was detected.  We use both edges (x2 quadrature)
//	as well as both edges on HALLB giving x4 quadrature
	if (!HALLA)
		{
		// A falling edge.

		if (!HALLB)
			{
			// Positive direction (arbitrary)
			position += rotation;
			}
		else
			{
			// Negative direction (arbitrary)
			position -= rotation;
			}

		}
	else
		{
		// A falling edge.

		if (HALLB)
			{
			// Positive direction (arbitrary)
			position += rotation;
			}
		else
			{
			// Negative direction (arbitrary)
			position -= rotation;
			}

		}
}

The below code looks nearly the same!

	if ((MCUCR & 0x01) == 0) // or ((MCUCR & ((1<<ICS01)|(1<<ICS00)) == (1<<ICS01))
		{
		// A falling edge.

		if (!HALLB)
			{
			// Positive direction (arbitrary)
			position += rotation;
			}
		else
			{
			// Negative direction (arbitrary)
			position -= rotation;
			}

		}
	else
		{
		// A falling edge.

		if (HALLB)
			{
			// Positive direction (arbitrary)
			position += rotation;
			}
		else
			{
			// Negative direction (arbitrary)
			position -= rotation;
			}

		}
//Change to other edge
	MCUCR ^= 0x01;  // or ^= (1<<ICS00)

So, only two differences: The check is on the interrupt-edge-selection-state, and the change of the edge.

Now, let's look at the generated code. See https://www.avrfreaks.net/index.p... . That is half of the routine, with a 16-bit position counter and use of register variables. Note the low ISR overhead.

Let's compare the nearly-identical edge-change:

_pin_change_isr2:
; .FSTART _pin_change_isr2
	ST   -Y,R26
	ST   -Y,R30
	IN   R30,SREG
	ST   -Y,R30
;  0000 000A // Single signal, both edges.
;  0000 000B //  Let's say it is PC0, with secondary channel on PC1
;  0000 000C //  I'll use "CodeVision-speak" for now
;  0000 000D //    if (PINC.0)
;  0000 000E 	if ((MCUCR & 0x01) == 0) // or ((MCUCR & ((1<<ICS01)|(1<<ICS00)) == (1<<ICS01))
	IN   R30,0x35
	SBRC R30,0
	RJMP _0x3
...
;  0000 002D //Change to other edge
;  0000 002E 	MCUCR ^= 0x01;  // or ^= (1<<ICS00)
	IN   R30,0x35
	LDI  R26,LOW(1)
	EOR  R30,R26
	OUT  0x35,R30

One more register to save/restore; a few more cycles and words to change the edge. Not a biggie, perhaps. But add 5 cycles to a 25-cycle ISR and now the max rate just dropped 20%. And to what end?

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi theusch,
Thanks for all your efforts, my 2 days of struggle and your experienced help made it work like a charm.

My ISR now looks like this

ISR(INT0_vect)
{

/* this works for MCUCR=0x01 i.e. interrupt on level change */

	// A raising edge
	if ((PIND & (1<<PD2))) {
		if ((PIND & (1<<PD0))) num++;	// B rising edge
		else num--;						// B falling edge
	}
	// A raising edge		
	else {
		if ((PIND & (1<<PD0))) num--;	// B rising edge
		else num++;						// B falling edge
	}
	
}

I attache the entire code here for anyone to refer.

One thing I am stuck is displaying negative numbers on 7 segment display. right now i just display that is positive number... well this isn't important for project to proceed.

Another interesting this is all this fails if I remove my simple hardware debounce using decoupling .1uF caps.

Probably if I use the edge selection method (which I tried and failed to implement again) that has a inherent software debounce I can get away without the caps.

But now I prefer to use the caps or a Schmitt-trigger in future.

Let me see if I can soon replace these mechanical encoders with the optical ones from the Ball mouse, alignment is an issue there... lets see.

Thanks again,
K

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi folks,

Zbairds tut is not available, can the link be updated please.

Regards

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Forum search suggests the link was probably to this:  https://www.avrfreaks.net/forum/tut-interfacing-2-bit-rotary-encoder-switches