Hallo mede-programmers,
In plaats van altijd maar jullie hulp in te roepen met problemen die ik heb, dacht ik nu eens wat te posten wat mogelijk voor jullie handig kan zijn.
Ik ben bezig met een projectje waarin ik een rotary encoder (zo een van Voti) gebruik om een menu in te stellen. Het probleem dat ik ondervond is het debouncen van zo'n apparaat in software. Ik heb verschillende methoden geprobeerd:
Alleen pollen van A, en bij een verandering B inlezen leek het handigst. Werkt wel, maar door dender zie je problemen (verhogen ipv verlagen, met 2 ipv 1 verhogen of verlagen etc.).
De volgend stap was een interrupt op een hooggande flank van A te gebruiken. Hierop een timer van 5mS te starten (de debounce tijd) en pin interrupts uit te schakelen. Als de timer afloopt, B inlezen, waarde afhankelijk van B verhogen of verlagen, en de pin interrupts weer aanzetten.
Wat denk je: werkt voor geen meter (slechter nog dan pollen). Logic analyzer aangesloten, en toen bleek waarom: deze encoders vertonen naast bouncing behoorlijk wat spikes als het contact gesloten is, met name wanneer je wat sneller draait (zweven van de contacten?).
Hier een plaatje van de logic analyzer:
Op de spikes reageert de Microcontroller interrupt, en resultaat is dan onvoorspelbaar.
Een methode die je ook regelmatig ziet is het pollen van de input op vaste tijdstippen (bv elke mS), en te tellen hoe vaak je dezelfde waarde ziet. Zie je een opeenvolgend aantal keren hetzelfde, dan kun je er vanuit gaan dat het signaal stabiel is, en kun je je actie uitvoeren.
Dit laatste heb ik nu ingebouwd (op 'n 20Mhz AVR), en werkt perfect. Ik poll elke mS (zie 3e kanaal in het plaatje hierboven), en als ik drie keer dezelfde waarde zie, ga ik er vanuit dat het signaal stabiel is. De verschijnselen die ik boven noem zie ik nu nooit meer, zelfs niet als ik probeer 'op de klik' een beetje heen en weer te bewegen.
Ik hoop dat jullie hier wat aan hebben !!
Op 4 oktober 2010 13:22:53 schreef maxva:
Ik hoop dat jullie hier wat aan hebben !!
Nou en of!
Onlangs kwam ik een tutorial-video tegen waar ook een debounce techniek voor een encoder uitgelegd werd.
Helaas kon ik'm naderhand niet meer vinden. Was wel volgens een ander principe.
In elk geval doe ik hier m'n voordeel mee. Bedankt.
Jochem
If you want to succeed, double your failure rate.
Mooie voor in het 'ontwerptips' topic, toch?
[OT]
Op 4 oktober 2010 13:28:49 schreef Lambiek:
Doe zoiets altijd met een potmeter, werkt ook perfect.
Zeker bruikbaar in sommige situaties, maar in een menu wil je 'door' kunnen draaien. Overigens zijn daar ook potmeters voor (SparkyGSX in het Lichtmis topic kocht ze per abuis als ik me goed herinner)
[/OT]
Roland van Leusden
It's the rule that you live by and die for It's the one thing you can't deny Even though you don't know what the price is. It is justified.
Anders een MC14490L Hex Contact Eliminator nemen, 1.00 @ Stuut en Bruin. In het datasheet van de MC14490L staat ook duidelijk beschreven hoe het werkt, kan je dus ook implementeren in een uC.
http://www.datasheetcatalog.org/datasheet/motorola/MC14490P.pdf
Echoflex
1
@Jeroon,
Zo heb ik het ook gedaan en werkt perfect!
Ik doe het onder interrupt en in de interrupt kijk ik of de schakelaar open of dicht is. Tot nu toe heb ik geen enkele hapering waar kunnen nemen, ook niet als ik absurd snel aan de encoder draai.
[code=C]
ISR (ROTARY_ENCODER)
{
bRotaryCounter = usDisplValue[ubMenuOption];
if(!bit_is_clear( ROTARY_ENCODER_PORT, ROTARY_ENCODER_PIN ))
{
bRotaryCounter -= Menu[ubMenuOption].flDspStepSize;
usDisplValue[ubMenuOption] -= Menu[ubMenuOption].flDspStepSize;
if(bRotaryCounter <= Menu[ubMenuOption].bMinDspValue)
{
bRotaryCounter = Menu[ubMenuOption].bMinDspValue;
usDisplValue[ubMenuOption] = Menu[ubMenuOption].bMinDspValue;
}
}
else
{
bRotaryCounter+=Menu[ubMenuOption].flDspStepSize;
usDisplValue[ubMenuOption] += Menu[ubMenuOption].flDspStepSize;
if(bRotaryCounter >= Menu[ubMenuOption].bMaxDspValue)
{
bRotaryCounter = Menu[ubMenuOption].bMaxDspValue;
usDisplValue[ubMenuOption] = Menu[ubMenuOption].bMaxDspValue;
}
}
//usDisplValue[ubMenuOption] = bRotaryCounter;
Menu[ubMenuOption].FunctionPtr(usDisplValue[ubMenuOption], ubMenuOption);
}
[/code=C]
Op 4 oktober 2010 14:28:26 schreef Roland van Leusden:
In het datasheet van de MC14490L staat ook duidelijk beschreven hoe het werkt, kan je dus ook implementeren in een uC.
Bedankt, handig om te weten...
Op 4 oktober 2010 15:15:18 schreef JER00N:
Kun je die spikes er niet uitfilteren met een simpel RC-laagdoorlaatfiltertje?
Vast wel, maar dan moet je met weerstands en/of condensator waarden gaan experimenteren om de goede waarden te vinden.
[Bericht gewijzigd door Henry S. op (36%)]
Op 4 oktober 2010 15:37:40 schreef Echoflex:
@Jeroon,Zo heb ik het ook gedaan en werkt perfect!
...
En hoe vaak roep je deze routine aan?
Dit is de code die ik nu gebruik. Timer loopt elke mS af, en wordt aan het eind van de ISR opnieuw geinitialiseerd.
ISR ( TIMER0_OVF_vect)
{
if ( Curr_A == 0 )
{
if ( bit_is_set(PINB, ROTENC_A) )
{
if ( ++DebCnt == DEBOUNCE_COMPLETE )
{
if ( bit_is_set( PINB, ROTENC_B ) )
value++;
else
value--;
Curr_A = 1;
DebCnt = 0;
}
}
else
DebCnt = 0;
}
else
{
if ( bit_is_clear(PINB, ROTENC_A) )
{
if ( ++DebCnt == DEBOUNCE_COMPLETE )
{
Curr_A = 0;
DebCnt = 0;
}
}
else
DebCnt = 0;
}
start_Timer0();
}
Echoflex
1
Zo kan het idd ook. Zelf gebruik ik geen timer interrupt maar een HW interrupt. Met 4.7k en 220n filter heb ik nog geen problemen ondervonden.
Een timer interrupt past niet in mijn project omdat ik belangrijke taken heb lopen die ik niet door polling wil onderbreken.
[Bericht gewijzigd door Echoflex op (32%)]
Op 4 oktober 2010 16:02:16 schreef Echoflex:
Met 4.7k en 220n filter heb ik nog geen problemen ondervonden.
Hoe ziet dat er uit in schema vorm? 4.7k pull-up vanaf de pin naar +5 en de 220n naar gnd? Ik dacht dat je voor zo'n filter twee weerstanden nodig had...
Edit: 2e post van echoflex niet goed gelezen, daar staat het antwoord in !!
Op 4 oktober 2010 16:02:16 schreef Echoflex:
Een timer interrupt past niet in mijn project omdat ik belangrijke taken heb lopen die ik niet door polling wil onderbreken.
Ik weet natuurlijk niet of en hoeveel processor tijd je over hebt naast je belangrijke taken, maar om je (en mijzelf) een idee te geven hoe lang het uitvoeren van zo'n ISR duurt op een 20Mhz AVR heb ik dat eens gemeten met de logic analyzer: tussen de 0.8 en 1.2 uS. Dus eigenlijk maar 1/1000000 van de beschikbare processortijd...
Jochem
If you want to succeed, double your failure rate.
Vaak gaat het om de voorspelbaarheid, meer dan om de totale tijd. Dus een interrupt die tussen belangrijke taken doorkomt zit je dan niet op te wachten.
Ik doe het met enkele 10nF parallel over de (voti) rotary encoder. werkt prima.
Nog een tip voor snel draaien: Verder gebruik ik een soort beybuffer (van 1 byte en een count) in de interrupt routine. Daarna de keybuf gebruiken om (buiten de ISR) de acties te ondernemen. Dit werkt tot zo snel als ik aan de rotary encoder kan draaien met een PIC op 8Mhz.
PROCEDURE int IS
PRAGMA INTERRUPT
if INTCON_INTF then
if RotB == low then
rotation = rotation - 1
else
rotation = rotation + 1
end if
keybuf = "R"
INTCON_INTF = false
end if
if INTCON_RBIF then
if RotP == low then
keybuf = "P"
end if
INTCON_RBIF = false
end if
return
END PROCEDURE
while true loop
if keybuf == "R" then -- Rotary rotated
var sdword freqdelta = multiplier * rotation
field_frequency = field_frequency + freqdelta
if freqdelta < 0 then
if field_frequency > 12_500_000 then
field_frequency = 0
end if
else
if field_frequency > 12_500_000 then
field_frequency = 12_500_000
end if
end if
do_write_freq = true
rotation = 0
end if
end loop
Ik doe veel (verschillende) dingen met de rotary (menu e.d.) en dit wil ik niet allemaal in de isr, vandaar de 'buffer'. Door het bijhouden van de 'rotation' werkt het ook nog als je meerdere interrupts krijgt tijdens het afhandelen van de keybuf='r'. Gemeten hoeveel dit kon zijn: rotation wordt meestal +-1, maar heel soms ook +- 3. Meer niet. Dit gaf de beste reponse op zeer snel draaien.
De rotarys van voti geven altijd twee hele pulsen per 'click', tussen de clicks door is hij dus altijd dubbel-laag. 4-voudige decoding heeft dus volgens mij niet zoveel zin?
RES
RES
Een methode is eerst op verandering pollen (kan via interrupt), dan interrrupt uitschakelen of negeren, dan circa 100 usec wachten (ondertussen kan je programma wat anders doen.), dan weer kijken naar hoog of laag dit circa vijf keer herhalen, als ie na die vijf keer nog steeds laag was dit aannemen, anders nogmaals 100 usec wachten.
Een andere methode is, 100 keer pollen met een hele korte vertraging (nsec's, lus) en dan tellen hoeveel keer je hoog en laag hebt, meer dan zeg tachtig laag die stand aannemen (dan is ie wel uitgestuitert)
Eenmaal de bounce weggefiltert in de software kan je nog een extra software filter overheen zetten, die de toestanden na elkaar moet zien, dus bijv. 1 click komt dit voorbij (AB) 01 11 10 00, dus een rollover buffer (met 3 of 4 compares) als de code daar alle 3 of 4 voorbij komt accepteren anders negeren (dan was dat niet een goede draai of net een halve click te ver doorgeschoten, als clickje bijv. terugvalt in oude stand, gebeurt wel eens. Hiermee krijg je zeer strakke clicks.
Echoflex
1
De software moet elke click zien als een correcte click, waarom 5 clicks controleren en als ze gelijk zijn een actie uitvoeren?
Als je nu gewoon 4k7 pullups en 220n naar aarde gebruikt en niet pollen maar interrupt gebruiken dan werkt het als een trein! Ik gebruik het al maanden zonder haperingen.
Jochem
If you want to succeed, double your failure rate.
Waarom vraag je? Kwestie van voorkeur.
In software een low-pass filter maken kan net zo goed als met een RC-netwerkje, het kost je alleen wat performance. Daarentegen spaart het geld en printruimte.
