Reading Rotary Encoders
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).
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. 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);
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
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> ;