Actions

Difference between revisions of "Reading Rotary Encoders"

From Just in Time

m (7 revisions: copying content from old site)
Line 1: Line 1:
 +
Many [[wikipedia:rotary encoder|rotary encoders]] work in quadrature mode. This means that their signals will look somewhat like what is shown on the right[[Image:Quadrature encoder.png|thumb|300px|typical quadrature signal]]. Note that the ones you turn by hand normally have one noticable 'click' for every full cycle (phases 1-4 in the image).
 +
==AVR code==
 +
The following code runs in a timer interrupt. Most timer frequencies will do, as long as it's fast enough to see all states in the interrupt. The code assumes a <code>read</code> function that reads the state of the two encoder output pins and puts them in the bottom two bits of the return value. Any two adjacent bits will do if the constant <code>0x02</code> is adapted.
 +
 +
<syntaxhighlight lang="cpp">
 +
/// Timer interrupt
 +
ISR( TIMER1_COMPA_vect)
 +
{
 +
// previously seen value of encoder bits.
 +
static uint8_t previous = 0;
 +
 +
// get the encoder state
 +
uint8_t current = read(encoder);
 +
if (current != previous)
 +
{
 +
// there's a change, decide whether we count up or down.
 +
if ((previous ^ (current << 1)) & 0x02)
 +
{
 +
--timer_state.value;
 +
}
 +
else
 +
{
 +
++timer_state.value;
 +
}
 +
timer_state.value_changed = 1;
 +
 +
//timer_state.value = current;
 +
previous = current;
 +
}
 +
 +
}
 +
</syntaxhighlight>
 +
==SX code==
 
The following SX28 assembly contains two virtual pheripherals:  
 
The following SX28 assembly contains two virtual pheripherals:  
 
* Reading a rotary encoder
 
* Reading a rotary encoder

Revision as of 14:25, 31 October 2010

Many rotary encoders work in quadrature mode. This means that their signals will look somewhat like what is shown on the right

typical quadrature signal

. Note that the ones you turn by hand normally have one noticable 'click' for every full cycle (phases 1-4 in the image).

AVR code

The following code runs in a timer interrupt. Most timer frequencies will do, as long as it's fast enough to see all states in the interrupt. The code assumes a read function that reads the state of the two encoder output pins and puts them in the bottom two bits of the return value. Any two adjacent bits will do if the constant 0x02 is adapted.

/// Timer interrupt
ISR( TIMER1_COMPA_vect)
{
	// previously seen value of encoder bits.
	static uint8_t previous = 0;

	// get the encoder state 
	uint8_t current = read(encoder);
	if (current != previous)
	{
		// there's a change, decide whether we count up or down.
		if ((previous ^ (current << 1)) & 0x02)
		{
			--timer_state.value;
		}
		else
		{
			++timer_state.value;
		}
		timer_state.value_changed = 1;

		//timer_state.value = current;
		previous = current;
	}

}

SX code

The following SX28 assembly contains two virtual pheripherals:

  • Reading a rotary encoder
  • driving 4 7-segment led displays

4x7segments 003.jpg

The main program just loops and does nothing. The first vp communicates directly with the second. This code is an adapted version (made into a vp) of sources found on sxlist.

;=======================================================================

;TITLE:         4x7segments.src
;
;PURPOSE:       Read pulses from a rotary encoder, adapt a counter
;			and output to 4 7-segment led displays
;
;AUTHOR:        Danny Havenith
;
;REVISIONS:
;  <mm/dd/yy> - <details of revision>
;               <more details of same revision>
;
;CONNECTIONS:
;	ra.0 and ra.1: encoder inputs
;	rc: led segment outputs
;	rb.0-4: led column outputs
;
;=======================================================================


;-------------------------- DEVICE DIRECTIVES --------------------------

		DEVICE		SX28,OSC4MHZ,TURBO
		DEVICE		STACKX, OPTIONX
		IRC_CAL		IRC_SLOW

		RESET		Initialize

;------------------------------ CONSTANTS ------------------------------

TicksPerMs	EQU	20	; interrupts per ms
CyclesPerTick	EQU	200	; cycles per interrupt


------------------------------ VARIABLES ------------------------------
			ORG	$10

BankLeds	= $

MSTimer		DS	1
CurrentDigit	DS	1
CurrentColumn	DS	1
Digits		DS	4

CounterA	DS	1
CounterB	DS	1
CounterC	DS	1

BankEncoder	= $
EncoderState	DS 	1
temp		DS	1


ColumnPort	EQU 	rb
RowPort		EQU	rc

EncoderPort	EQU	ra 	; port for the rotary encoder
EncoderMask	EQU	$03	; which bits to use.

;---------------------------- DEBUG SETTINGS ---------------------------

		FREQ	4_000_000
	
;		WATCH	<Symbol>,<bit count>,<format>

WKED_W		equ	$0A		;Write MIWU/RB Interrupt edge setup, 0 = falling, 1 = rising
WKEN_W		equ	$0B		;Write MIWU/RB Interrupt edge setup, 0 = enabled, 1 = disabled
ST_W		equ	$0C		;Write Port Schmitt Trigger setup, 0 = enabled, 1 = disabled
LVL_W		equ	$0D		;Write Port Schmitt Trigger setup, 0 = enabled, 1 = disabled
PLP_W		equ	$0E		;Write Port Schmitt Trigger setup, 0 = enabled, 1 = disabled
DDIR_W		equ	$0F		;Write Port Direction

RA_latch	equ	%00000000		;SX18/20/28/48/52 port A latch init
RA_DDIR		equ	%11111111		;see under pin definitions for port A DDIR value
RA_LVL		equ	%00000000		;SX18/20/28/48/52 port A LVL value
RA_PLP		equ	%11111111		;SX18/20/28/48/52 port A PLP value

RB_latch	equ	%00000000		;SX18/20/28/48/52 port B latch init
RB_DDIR		equ	%11110000		;SX18/20/28/48/52 port B DDIR value
RB_ST		equ	%11111111		;SX18/20/28/48/52 port B ST value
RB_LVL		equ	%00000000		;SX18/20/28/48/52 port B LVL value
RB_PLP		equ	%11111111		;SX18/20/28/48/52 port B PLP value

RC_latch	equ	%00000000		;SX18/20/28/48/52 port C latch init
RC_DDIR		equ	%00000000		;SX18/20/28/48/52 port C DDIR value
RC_ST		equ	%11111111		;SX18/20/28/48/52 port C ST value
RC_LVL		equ	%00000000		;SX18/20/28/48/52 port C LVL value
RC_PLP		equ	%11111111		;SX18/20/28/48/52 port C PLP value

;-------------------------- INTERRUPT ROUTINE --------------------------
		ORG	$0
		
;	break Interrupt
Interrupt
; timer
	bank BankLeds
	mov w, #TicksPerMs
	dec MSTimer
	snz
	mov MSTimer, w
	sz
	jmp EndInterrupt

	mov w, #Digits
	add w, CurrentDigit
	mov fsr, w
	mov w, IND
	call Decode
	clr ColumnPort
	mov RowPort, w
	mov ColumnPort, CurrentColumn

	; next column
	clc
	rr CurrentColumn	; rotate column left
	mov w, #%00001000
	inc CurrentDigit		; increase digit counter
	snc 		; reset if digit = 4
	mov CurrentColumn, w
	snc 
	clr CurrentDigit

:EndLeds

	break ReadEncoder
ReadEncoder
        mov w, EncoderPort           ;get change between current and previous 
                            ;encoder state in w
        xor w, EncoderState    ;
        
        xor EncoderState, w    ;update state and preserve difference in w

        and w, #EncoderMask         ;check if there is change
        snz
         jmp :EndEncoder       ;no change, read encoder again

        ;if both bits changed, this will be an error, but we ignore it here
        ;xor w, #$03
        ;skpnz
        ; jmp enc_error

        ;calculate direction in temp.1
        mov temp, w       
        mov w, <<EncoderState
        xor w, EncoderState
        xor temp, w

        sb temp.1
         call EncoderIncrease       ;Encoder moved up
        snb temp.1
         call EncoderDecrease       ;Encoder moved down

:EndEncoder

EndInterrupt
	mov w, #-CyclesPerTick		
	retiw

EncoderIncrease
	inc	Digits + 3
	cjne	Digits + 3, #10, :EndInc
	clr 	Digits + 3

	inc	Digits + 2
	cjne	Digits + 2, #10, :EndInc
	clr 	Digits + 2

	inc	Digits + 1
	cjne	Digits + 1, #10, :EndInc
	clr 	Digits + 1

	inc	Digits 
	cjne	Digits, #10, :EndInc
	clr 	Digits
:EndInc
	retp

EncoderDecrease
	dec	Digits + 3
	cjne	Digits + 3, #$FF, :EndDec
	mov	Digits + 3, #09

	dec	Digits + 2
	cjne	Digits + 2, #$FF, :EndDec
	mov	Digits + 2, #09

	dec	Digits + 1
	cjne	Digits + 1, #$FF, :EndDec
	mov	Digits + 1, #09

	dec	Digits
	cjne	Digits, #$FF, :EndDec
	mov	Digits, #09
:EndDec		
	retp

Decode
	and w, #$0F
	jmp PC+w
	retw %00000101 ;0
	retw %11011101 ;1
	retw %10000110 ;2
	retw %10010100 ;3
	retw %01011100 ;4
	retw %00110100 ;5
	retw %00100100 ;6
	retw %10011101 ;7
	retw %00000100 ;8
	retw %00010100 ;9
	retw %00001100 ;A
	retw %01100100 ;b
	retw %00100111 ;C
	retw %11000100 ;d
	retw %00100110 ;E
	retw %00101110 ;F

;------------------------ INITIALIZATION ROUTINE -----------------------

Initialize
		; Configure all ports
		mov 	m, #ST_W			;point MODE to write ST register
		mov     w,#RB_ST            	;Setup RB Schmitt Trigger, 0 = enabled, 1 = disabled
		mov	!rb,w		
		mov     w,#RC_ST            	;Setup RC Schmitt Trigger, 0 = enabled, 1 = disabled
		mov	!rc,w	
		mov 	m, LVL_W			;point MODE to write LVL register
		mov     w,#RA_LVL            	;Setup RA CMOS or TTL levels, 0 = TTL, 1 = CMOS
		mov	!ra,w		 
		mov     w,#RB_LVL            	;Setup RB CMOS or TTL levels, 0 = TTL, 1 = CMOS
		mov	!rb,w		
		mov     w,#RC_LVL            	;Setup RC CMOS or TTL levels, 0 = TTL, 1 = CMOS
		mov	!rc,w	

		mov 	w,#RA_PLP            	;Setup RA Weak Pull-up, 0 = enabled, 1 = disabled
		mov	!ra,w		 
		mov     w,#RB_PLP            	;Setup RB Weak Pull-up, 0 = enabled, 1 = disabled
		mov	!rb,w		
		mov     w,#RC_PLP            	;Setup RC Weak Pull-up, 0 = enabled, 1 = disabled
		mov	!rc,w	

		mov 	m, #DDIR_W			;point MODE to write DDIR register
		mov	w,#RA_DDIR		;Setup RA Direction register, 0 = output, 1 = input		
		mov	!ra,w	
		mov	w,#RB_DDIR		;Setup RB Direction register, 0 = output, 1 = input
		mov	!rb,w			
		mov	w,#RC_DDIR		;Setup RC Direction register, 0 = output, 1 = input
		mov	!rc,w			

		mov     w,#RA_latch          	;Initialize RA data latch
		mov     ra,w		
		mov     w,#RB_latch         	;Initialize RB data latch
		mov     rb,w		
		mov     w,#RC_latch          	;Initialize RC data latch
		mov     rc,w		

; zero all ram (SX28)
		clr	fsr			;reset all ram banks
:zero_ram	sb	fsr.4			;are we on low half of bank?
		setb	fsr.3			;If so, don't touch regs 0-7
		clr	ind			;clear using indirect addressing
		incsz	fsr			;repeat until done
		jmp	:zero_ram

:init_leds	bank BankLeds
		mov CurrentDigit, #0
		mov CurrentColumn, #1

;---------------------------- MAIN PROGRAM -----------------------------

Main

	mov Digits, #1
	mov Digits + 1, #2
	mov Digits + 2, #3
	mov Digits + 3, #4
	mov	!option, #%10011111	;enable rtcc interrupt

:loop	
 	jmp :loop




	
;----------------------------- SUBROUTINES -----------------------------

;<GlobalLabel>
;<detailed description of routine>

;		<inst>	<op1>,<op2>		;