### From Just in Time

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

. Note that the ones you turn by hand normally have one noticable 'click' for every full cycle (phases 1-4 in the image). Whether the pins are low-active, or high-active and which pin is which is normally not important, though it does influence which direction is considered 'up' and which is 'down'.

## 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. Of course, you can also use a pin change interrupt if you want to be sure. 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. Do take care that `timer.state` in this example counts up or down for every state change, which means that the counter is increased four times for every click.

``````/// 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); // this reads the A and B line values in the lower 2 bits.
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;
}

}``````

The code works by comparing line A at time t with line B at time t-1. Looking at the drawing above, you can see that when we move to the right in this diagram, the state of line A seems to follow that of line B, which means that A(t) == B(t-1) if the encoder moves to the right. Conversely, if the encoder moves to the left A(t) will always be the opposite of B(t-1). An exclusive OR of A(t) and B(t-1) is used to compare the two values.

## SX code

The following SX28 assembly contains two virtual pheripherals:

• driving 4 7-segment led displays

Reading the encoder starts at label `ReadEncoder`.

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
;
;			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
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

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>		;<time>	<comment>``````