Rotationssensor
- JimmyAndersson
- Inlägg: 26571
- Blev medlem: 6 augusti 2005, 21:23:33
- Ort: Oskarshamn (En bit utanför)
- Kontakt:
Såg att min D-flipflop (74LS374N) saknade separata CKL och CLR-pinnar så lösningen med flipflop sprack. Ska testa Icecap's lösning istället. Den ser mycket smidig och enkelt ut.
Eftersom alla mina "vanliga" interruptpinnar är upptagna så måste jag använda de som ger "interrupt on change". Men då ger B bara 1, 0, 1, 0 (vid interrupt på A) oavsett vilket håll man snurrar. Men om jag läser av både A och B så verkar det fungera fint.
Vrider åt ena hållet:
AB
00 = 0 <-- Ett hack (eller "Dentent stability point".)
10 = 2 <-- Ger interrupt
11 = 3 <-- Nästa hack
01 = 1 <-- Ger interrupt
00 = 0 <-- Hack igen
10 = 2 <-- Ger interrupt
osv..
Andra hållet:
AB
00 = 0 <-- Hack. Ger interrupt
01 = 1
11 = 3 <-- Nästa hack. Ger interrupt
10 = 2
00 = 0 <-- Hack igen. Ger interrupt
01 = 1
När det blir interrupt ser det alltså ut såhär:
Ena hållet: 1, 2, 1, 2. Andra hållet: 0, 3, 0, 3.
Nu ska jag bara knappa lite kod och se hur det fungerar i praktiken.
Eftersom alla mina "vanliga" interruptpinnar är upptagna så måste jag använda de som ger "interrupt on change". Men då ger B bara 1, 0, 1, 0 (vid interrupt på A) oavsett vilket håll man snurrar. Men om jag läser av både A och B så verkar det fungera fint.
Vrider åt ena hållet:
AB
00 = 0 <-- Ett hack (eller "Dentent stability point".)
10 = 2 <-- Ger interrupt
11 = 3 <-- Nästa hack
01 = 1 <-- Ger interrupt
00 = 0 <-- Hack igen
10 = 2 <-- Ger interrupt
osv..
Andra hållet:
AB
00 = 0 <-- Hack. Ger interrupt
01 = 1
11 = 3 <-- Nästa hack. Ger interrupt
10 = 2
00 = 0 <-- Hack igen. Ger interrupt
01 = 1
När det blir interrupt ser det alltså ut såhär:
Ena hållet: 1, 2, 1, 2. Andra hållet: 0, 3, 0, 3.
Nu ska jag bara knappa lite kod och se hur det fungerar i praktiken.

Gjorde en rutin för att läsa av en rot.sensor till en PIC18 för något
år seden. Det var typen som man kan trycka på, men det är ganska
ointressant, det är ju bara en kontakt som lika gärna (utifrån PIC'ens
horizont) kunde vara vilken switch som helst.
Hur som helst, med ett timerinterrupt läste jag av den ca 400 gånger/sek.
Sedan var det bara att jämföra det gamla och nya statuset och avgöra
vilken riktinng den hade vridit sig. Att den ibland (mest om man vred lite
snabbare) missade eller fick ett steg i fel riktning spelar ingen som helst roll,
i praktiskt bruk är det inget som märks. När man vred sakta missade den inte.
Jag hade och en accellerationsfunktion i den, om man vred snabbare
än en viss hastiget, så räknade programvaran dubbla eller tredubbla
"klick" för varje steg. Praktiskt.
Fördelen med pollning i detta fall, är att det blir små problem med
kontaktstudsar, om man kör interruptlösninge så *kan* man få en
del problem med det, det beror lite på kvaliteten på sensorn och
eventuell filtrering av signalerna med RC länkar.
år seden. Det var typen som man kan trycka på, men det är ganska
ointressant, det är ju bara en kontakt som lika gärna (utifrån PIC'ens
horizont) kunde vara vilken switch som helst.
Hur som helst, med ett timerinterrupt läste jag av den ca 400 gånger/sek.
Sedan var det bara att jämföra det gamla och nya statuset och avgöra
vilken riktinng den hade vridit sig. Att den ibland (mest om man vred lite
snabbare) missade eller fick ett steg i fel riktning spelar ingen som helst roll,
i praktiskt bruk är det inget som märks. När man vred sakta missade den inte.
Jag hade och en accellerationsfunktion i den, om man vred snabbare
än en viss hastiget, så räknade programvaran dubbla eller tredubbla
"klick" för varje steg. Praktiskt.
Fördelen med pollning i detta fall, är att det blir små problem med
kontaktstudsar, om man kör interruptlösninge så *kan* man få en
del problem med det, det beror lite på kvaliteten på sensorn och
eventuell filtrering av signalerna med RC länkar.
Nja, det är flera olika ASM filer, och uppbyggt med Olin Lathrop's
ASM-utvecklingsmiljö för PIC, så om man inte är van att jobba
i den så kan det vara lite tuft att läsa...
Om någon är intresserad av Olin's miljö, så har jag ställt samman
en PDF beskrivning som finns här : http://www.jescab.se/embedinc.htm
Notera att dokumentet är några år gammal och Olin har uppdaterat
en hel del, bl.a med dsPIC support.
Olin's kit finns här : http://www.embedinc.com/pic/
för den som är intresserad...
Olin själv syns här : http://www.embedinc.com/manage.asp
ASM-utvecklingsmiljö för PIC, så om man inte är van att jobba
i den så kan det vara lite tuft att läsa...
Om någon är intresserad av Olin's miljö, så har jag ställt samman
en PDF beskrivning som finns här : http://www.jescab.se/embedinc.htm
Notera att dokumentet är några år gammal och Olin har uppdaterat
en hel del, bl.a med dsPIC support.
Olin's kit finns här : http://www.embedinc.com/pic/
för den som är intresserad...
Olin själv syns här : http://www.embedinc.com/manage.asp

Eftersom jag bara för några dagar sedan skrev en rutin för att läsa av rotationssensorer i assembler så kan jag ju posta den här, kanske den hjälper (eller stjälper?
) någon. Hur som helst är den snabbt testad och den fungerar någorlunda bra för mig. Det finns några småsaker som borde fixas ännu förrän allt är perfekt, men som sagt, det fungerar.
Själv använder jag PORTB interrupten för att avläsa rotationssensorn, men rutinen i sig ger blanka f*n i hur den matas med data.
Jag förklarar inte koden speciellt här, eftersom jag när jag skrev den försökte få till hyfsade kommentarer i koden (på engelska).
P.S. Om inlägget blev för långt kan jag lägga upp koden på mitt webbutrymme istället.

Själv använder jag PORTB interrupten för att avläsa rotationssensorn, men rutinen i sig ger blanka f*n i hur den matas med data.

Jag förklarar inte koden speciellt här, eftersom jag när jag skrev den försökte få till hyfsade kommentarer i koden (på engelska).
Kod: Markera allt
cblock H'20'
ROT_TMP ; Temporary byte for rotary encoder functions
ROT_INPUT ; Input of rotary encoder passed to ParseRotaryEncoder
ROT_STATE ; State of the rotary encoder, both passed and returned
ROT_VAL ; Value to be incremented or decremented each "complete
; turn" or the encoder. CW increments, CCW decrements.
endc
; *** ParseRotaryEncoder ***************************************************** ;
; ParseRotaryEncoder will compare input from ROT_INPUT and ROT_STATE, to see
; wheter the rotary encoder has moved in any (valid) direction since the last
; reading. It will update ROT_STATE to reflect the new state unless the input is
; the same as before.
;
; Explanation of bits ROT_INPUT and ROT_STATE
; ROT_INPUT: Contains nothing but 2 bit input of encoder in bits <0:1>. Period.
; ROT_STATE: Bits <0:1> contains old input of encoder (as compared to new in
; ROT_INPUT <0:1>. Bits<6:7> contains the moving direction of the
; encoder, where bit<6>=0 means CW and bit<6>=1 means CCW. Bit<7> is
; set if we are moving in any direction (usually ROT_INPUT != 0).
; **************************************************************************** ;
ParseRotaryEncoder:
; Test if new input is the same as old one, and if it is, return without
; doing anything else.
movf ROT_STATE, W
andlw H'3'
subwf ROT_INPUT, W
btfsc STATUS, Z
return ; Same input, return
; Calculate the moving direction, and at the same time we check
; wheter the "direction" is valid. We do this by putting the old input
; in W<2:3> and new input in W<0:1> and calling a lookup table.
movf ROT_STATE, W
andlw H'3'
movwf ROT_TMP
bcf STATUS, C ; Don't shift in unwanted carry flag
rlf ROT_TMP, F
rlf ROT_TMP, W
iorwf ROT_INPUT, W
call _ParseRotaryEncoder_DirectionTable
xorlw H'1' ; Change the direction since we did
; the lookup in "reverse" order
; Check wheter the moving direction is valid (make sure we didn't miss
; any inputs in between readings).
movwf ROT_TMP
btfss ROT_TMP, 1 ; Bit 1 will be set if rotation is valid
goto _ParseRotaryEncoder_ResetAndEnd
; Make sure we are moving in the same direction as before (just compare
; ROT_STATE<6:7> with newly returned direction).
bcf STATUS, C
rlf ROT_TMP, F
rlf ROT_TMP, F
swapf ROT_STATE, W
andlw H'C'
subwf ROT_TMP, W
btfss STATUS, Z
goto _ParseRotaryEncoder_TestFirstMove ; Direction not same...
call _ParseRotaryEncoder_SaveNewInput ; Same direction
; Test if new input is 00, which marks a "complete turn". Increment/
; decrement ROT_VAL every turn. Direction is determined by checking
; ROT_STATE<6>
movf ROT_INPUT, F
btfss STATUS, Z
return ; Still not a complete turn; return.
btfss ROT_STATE, 6
incf ROT_VAL ; CW direction, inc
btfsc ROT_STATE, 6
decf ROT_VAL ; CCW direction, dec
goto _ParseRotaryEncoder_ResetAndEnd
; === _ParseRotaryEncoder_SaveNewInput ======================================= ;
; Save new input from ROT_INPUT to ROT_STATE
; ============================================================================ ;
_ParseRotaryEncoder_SaveNewInput:
movlw H'FC'
andwf ROT_STATE
movf ROT_INPUT, W
iorwf ROT_STATE, F
return
; === _ParseRotaryEncoder_TestFirstMove ====================================== ;
; Test if ROT_STATE is uninitialized. If is is the movement is valid and we
; update it to reflect the new data. Otherwise reset ROT_STATE and return.
; ============================================================================ ;
_ParseRotaryEncoder_TestFirstMove:
movf ROT_STATE, W
andlw H'C0'
btfss STATUS, Z
goto _ParseRotaryEncoder_ResetAndEnd
; Save direction
swapf ROT_TMP, F
movlw H'3F'
andwf ROT_STATE, F
movf ROT_TMP, W
iorwf ROT_STATE, F
call _ParseRotaryEncoder_SaveNewInput ; Save input
goto _PortBInterrupt_End
; === _ParseRotaryEncoder_ResetAndEnd ======================================== ;
; Reset ROT_STATE and return
; ============================================================================ ;
_ParseRotaryEncoder_ResetAndEnd:
movlw H'0'
movwf ROT_STATE
return
; === _ParseRotaryEncoder_DirectionTable ===================================== ;
; W should contain old direction in bits<2:3> and new direction in bits<0:1>.
; We then return a value through W describing the direction, where 00 means no
; change or moving too fast. 01 and 11 for CW and CCW direction, respectively.
; ============================================================================ ;
_ParseRotaryEncoder_DirectionTable:
addwf PCL, 1
retlw B'00' ; 00 => 00, no change
retlw B'10' ; 00 => 01, CW
retlw B'11' ; 00 => 10, CCW
retlw B'00' ; 00 => 11, moving too fast
retlw B'11' ; 01 => 00, CCW
retlw B'00' ; 01 => 01, no change
retlw B'00' ; 01 => 10, moving too fast
retlw B'10' ; 01 => 11, CW
retlw B'10' ; 10 => 00, CW
retlw B'00' ; 10 => 01, moving too fast
retlw B'00' ; 10 => 10, no change
retlw B'11' ; 10 => 11, CCW
retlw B'00' ; 11 => 00, moving too fast
retlw B'11' ; 11 => 01, CCW
retlw B'10' ; 11 => 10, CW
retlw B'00' ; 11 => 11, no change
- JimmyAndersson
- Inlägg: 26571
- Blev medlem: 6 augusti 2005, 21:23:33
- Ort: Oskarshamn (En bit utanför)
- Kontakt:
Min C kod för quadrature avläsning via interrupt på AVR:
Hoppas den är användbar!
Kod: Markera allt
ISR(INT0_vect) //Channel A
{
if(bit_is_set(PIND, PD2)) //Rising edge
{
if(bit_is_set(PIND, PD3)) //If B is high decrement
{
ProcessVariable--;
} else { //Else increment
ProcessVariable++;
}
} else { //Falling edge
if(bit_is_set(PIND, PD3)) //If B is high increment
{
ProcessVariable++;
} else { //Else decrement
ProcessVariable--;
}
}
}
ISR(INT1_vect) //Channel B
{
if(bit_is_set(PIND, PD3)) //Rising edge
{
if(bit_is_set(PIND, PD2)) //If A is high increment
{
ProcessVariable++;
} else { //Else decrement
ProcessVariable--;
}
} else { //Falling edge
if(bit_is_set(PIND, PD2)) //If A is high decrement
{
ProcessVariable--;
} else { //Else increment
ProcessVariable++;
}
}
}
Som en del kanske vet har jag lagt till en enkoder för positionsåtermatning för vagnen på min inverterade pendel (har inte lagt upp de senaste bilderna eller resultaten än i tråden. Det kommer
).
Eftersom jag där körde med en mekanisk enkoder ville jag minimera risken för kontaktstudsar och körde precis som Sodjan med pollad avläsning med tillägget att jag hade räknare som räknade hur många ggr i rad som A och B varit hög resp låg. (4 räknare, dvs ggr A=0, A=1, B=0, B=1). Sen hade jag bara en tröskel på hur många gånger i rad en avläsning behövde vara stabil för att jag skulle lita på den i min upp/ner-räknare.
Den lösningen gav en bra flexibilitet eftersom man kan 'träjda' robusthet mot kontaktstudsar mot maximal rotationshastighet.
Gjort i AVR asm. Kan posta den senare när jag städat det lite om någon är intresserad...

Eftersom jag där körde med en mekanisk enkoder ville jag minimera risken för kontaktstudsar och körde precis som Sodjan med pollad avläsning med tillägget att jag hade räknare som räknade hur många ggr i rad som A och B varit hög resp låg. (4 räknare, dvs ggr A=0, A=1, B=0, B=1). Sen hade jag bara en tröskel på hur många gånger i rad en avläsning behövde vara stabil för att jag skulle lita på den i min upp/ner-räknare.
Den lösningen gav en bra flexibilitet eftersom man kan 'träjda' robusthet mot kontaktstudsar mot maximal rotationshastighet.
Gjort i AVR asm. Kan posta den senare när jag städat det lite om någon är intresserad...