Department of InformatiX
Microsoft .NET Micro Framework Tools & Resources

Máme už všechno připraveno k tomu, abychom završili naši sérii příspěvků o tlačítkách a rozšiřování vstupního systému připojením maticové klávesnice.

Princip maticových klávesnic

Maticová klávesnice je několik tlačítek zapojených do matice, majících jeden vývod pro každý sloupeček a jeden pro každý řádek. Například máme-li 12 tlačítek uspořádaných do matice 3×4, má klávesnice 7 vývodů. Stisknutím tlačítka se spojí vodiče příslušného sloupečku a řádku. Podrobné stránky s ilustracemi o tom, jak maticová klávesnice funguje a jak zjistit, které tlačítko je stisknuté, dal dohromady David Dribin. Ačkoliv jsem dostal svolení je přeložit do češtiny, rozhodl jsem se nejdříve zveřejnit tento článek - použitá angličtina je velmi jednoduchá a věřím, že nebude dělat potíže. Dále tedy předpokládám, že máte představu jak maticové klávesnice fungují a kdy se tlačítka navzájem zastiňují (ghosting). Maticové klávesnice obvykle nemívají v sobě žádnou elektroniku (pouze vodiče), takže je můžete připojit přímo na procesor nebo kamkoliv potřebujete.

STD 34-07 a STD 44-08

Na trhu lze dnes najít spoustu druhů maticových klávesnic. Já jsem použil univerzální membránovou klávesnici od Transfer Multisort Elektronik za cenu kolem sta korun. Prodává se s DIP konektorem a model STD 44-08 vypadá asi takto:

schéma STD 44-80snímek STD 44-08

Model STD 34-07 je stejný, akorát nemá poslední sloupeček (takže konektor má pouze 7 pinů) a oba se dají pořídit s pasujícími umělohmotnými krabičkami od firmy Hammond Mfg.

Dostupná tlačítka jsem shrnul do tohoto enumu:

public enum KeypadButton { Shift = Button.Menu, Enter = Button.Select, Up = Button.Up, Left = Button.Left, Right = Button.Right, Down = Button.Down, Decimal = Button.Pause, Clear = Button.Back, Add = Button.Play, Multiply = Button.FastForward, Substract = Button.Stop, Divide = Button.Rewind, NumPad0 = Button.Last + 1, NumPad1, NumPad2, NumPad3, NumPad4, NumPad5, NumPad6, NumPad7, NumPad8, NumPad9, F1, F2, F3, F4, F5 }

Všimněte si, že jsou zde všechny tlačítka, které mohou klávesy reprezentovat, nikoliv klávesy samotné (např. je zde NumPad7 i F2), a že je použito maximum stávajících tlačítek (s trochou představivosti i odpovídajícím významem).

MatrixKeyboard ovladač

Ovladač je možné stáhnout zde. Jeho základní schéma vypadá následovně:

schéma třídy MatrixKeyboard

MatrixKeyboard je hlavní třídou, všechny ostatní jsou vnořené. Struktura KeyLocation jen uchovává pár sloupec-řádek, nic zvláštního. Podporuje porovnávání a lze ji převádět na typ long s zpět, jelikož jednotlivé souřadnice jsou typu int.

Třída KeyStateArray uchovává informace o tom, které klávesy jsou stisknuté a které ne. V podstatě jde tedy o pamatování si hodnoty true/false pro každý prvek v matici, tj. bool[,] state. Nicméně vícerozměrná pole k dispozici nemáme a ukládání typu bool takovýmto způsobem by nebylo příliš efektivní. Třída proto raději využívá jednotlivé bity v poli integerů - i stav klávesnice s 5×6 tlačítky vyžadující 30 bitů může být uložen v jediném integeru. Navíc lze pomocí indexeru a trochou posouvání a maskování zachovat přístup k datům pomocí syntaxe state[row, column].

KeyStateArray obsahuje ještě dvě metody stojící za zmínku. Ta první z nich, static IsCompatible(KeyStateArray a, KeyStateArray b) je jednoduchá - jen kontroluje, zda jsou dvě KeyStateArray určeny pro shodný počet sloupců a řádků. Tou nejdůležitější je však

public static void FindChanges(KeyStateArray oldState, KeyStateArray newState, out ArrayList pressed, out ArrayList released).

Má za úkol porovnat dvě (kompatibilní) KeyStateArray a vypsat rozdíly. První ArrayList obsahuje seznam souřadnic KeyLocation, na kterých je v oldState hodnota false a v newState true (klávesa byla stisknuta). Ten druhý obsahuje seznam souřadnic s opačným rozdílem (klávesa byla uvolněna).

Nyní se podívejme, jak třída MatrixKeyboard funguje:

StartListening
Konstruktor
inicializace portů
spuštění vlákna událostí
Vlákno událostí
vyvolání
událostí
čekání na
signál
vyprázdnit
seznam čekatelů
Přerušení na jakémkoliv portu
stop listeningodhlásit přerušení
vypnutí výstupů
get current state
find changes
pro každý výstup
přečíst vstupy
přidat změny na seznam čekatelů
start listeningzapnout výstupy
přihlásit přerušení
dát signál vláknu událostí
EventKeyDown
EventKeyUp

Prvním krokem použití ovladače je jeho vytvoření jeho instance (lze připojit i více klávesnic současně). K zavolání konstruktoru potřebujete seznam pinů na kterých jsou vodiče představující řádky, seznam pinů na kterých jsou vodiče sloupců a pak režim resistorů, který váš procesor podporuje. Ovladač je schopen pracovat jak s pull-up tak s pull-down resistory, ale vyžaduje, aby procesor podporoval přerušení na obě hrany současně, včetně - pokud jej nevypnete - filtru proti zákmitům. Konstruktor se rozhodne, zda aktivní (výstupní) porty budou řádky nebo sloupce, což ovlivňuje dvě věci: Za prvé výkon - změna stavu na výstupním portu je oproti čtení vstupního portu časově velmi náročná, takže bychom rádi měli pokud možno co nejméně aktivních portů. Drouhou věcí je stínění tlačítek. Bez dalších kroků zaměřených proti tomuto jevu je možné chytit změnu pouze jednoho tlačítka v pasivním směru, takže bychom pasivních portů měli rádi co nejvíce. To je skvělé, ne každý den se požadavky nerozcházejí, takže postup je snadný - aktivní bude směr s méně vodiči. Máte-li nějaký důvod zvolit opačné řešení, stačí upravit hodnotu _activeAreRows. Pak jsou porty inicializovány a vlákno událostí, kte kterému se ještě vrátíme, je vytvořeno a spuštěno.

Tedy za předpokladu, že máte klávesnici 3×4 a a piny definovaná pomocí Cpu.Pin konstant řekněme ve statické třídě Connected:

MartixKeyboard Keypad = new MatrixKeyboard( new Cpu.Pin[] { Connected.Row1, Connected.Row2, Connected.Row3, Connected.Row4 }, new Cpu.Pin[] { Connected.Column1, Connected.Column2, Connected.Column3 }, Port.ResistorMode.PullUp);

Stále toho však ovladač nedělá mnoho. Musíme spustit monitorování portů:

Keypad.StartListening();

Tato metoda zapne všechny aktivní porty a připojí handlery přerušení na všechny pasivní porty (v tomto pořadí, jinak bychom si přerušení způsobili sami). Pasivní porty jsou plovoucí dokud není nějaká klávesa stisknuta (proto se hodí mít pull-up/pull-down resistory). Co se tedy stane při stisknutí klávesy? Stav ze zapnutého aktivního portu se dostane na nějaký pasivní, což způsobí přerušení. Běhěm přerušení je monitorování zastaveno - handlery jsou odhlášeny a všechny aktivní porty se vypnou (v tomto pořadí...).

Potom se zavolá metoda GetCurrentState, která proskenuje stav klávenice a výsledek uloží do KeyStateArray. Ve skutečnosti můžete používat ovladač v "manuálním" režimu, totiž že nespustíte monitorování a budete jen ručně kontrolovat stav pomocí této metody. Skenování probíhá tak, že se postupně zapne každý jednotlivý aktivní port, a přečte se stav na všech pasivních; více podrobností lze najít na Davidových stránkách. Aktuální stav je porovnán s předchozím pomocí zmíněné metody FindChanges a změny jsou v podobě třídy EventBufferItem přidány do seznamu (čekatelů) _eventBuffer. Tato třída slouží jen k předání informací nutných k vyvolání událostí KeyPressed/KeyReleased. Nakonec je spícímu vláknu signalizována změna tohoto seznamu.

Jediným úkolem vlákna událostí je vyvolávat události. Jeho startovní metoda je EventThread() a většinu svého času stráví čekáním na signál. Jakmile signál dorazí, začne vyzvedávat položky z _eventBufferu (ve smyslu FIFO) a vyvolávat příslušné události, dokud seznam opět nevyprázdní.

Pokud byste chtěli chytat v pasivním směru více než jednu klávesu, museli byste neustále skenovat klávesnici ve smyčce, dokud nebudou všechny klávesy uvolněny. To by ale představovalo velké zatížení pro procesor, které si myslím za to nestojí. Domníváte-li se že ano, dejte vědět, pokud by se vám implementace nedařila.

MatrixKeyboardInputProvider

Teď zbývá vzít informaci o stisknuté klávese a odeslat ji jako událost ButtonDown pro tlačítko, které klávesa reprezentuje. To je úkol pro input providera, na kterého se můžete podívat zde. Skládá se ze dvou částí. Z inicializační, obsahující metody RegisterButton a RegisterModifier a z provozní, která sestává zejména s obsluhou KeyDown a KeyUp událostí ovaladače. Takto vypadá jeho schéma:

schéma třídy MatriKeyboardInputProvider

Detaily o vkládání rozšířeného rozsahu tlačítek do systému WPF (metody ReportButton a RaiseEvent) jsme probrali v článku Prokletí třinácti tlačítek.

Podívate-li se na představenou maticovou klávesnici, zjistíte, že některé klávesy mohou mít různé významy podle toho, zda je či není stisknuta klávesa Shift. Pro případ, že vaše klávesnice má i jiné modifikátory jsem převzal celý enum z WPF:

namespace System.Windows.Input { [Flags] public enum ModifierKeys { None = 0, Alt = 1, Control = 2, Shift = 4, Windows = 8 } }

Abychom to shrnuli, potřebujeme vlastně namapovat Button na KeyLocation. To lze udělat pomocí metody RegisterButton:

KeypadInput = new MatrixKeyboardInputProvider(Keypad, null); KeypadInput.RegisterButton(new MatrixKeyboard.KeyLocation(3, 2), ModifierKeys.None, (Button)KeypadButton.Enter); KeypadInput.RegisterButton(new MatrixKeyboard.KeyLocation(3, 2), ModifierKeys.Shift, (Button)KeypadButton.Clear);

Tento kód říká, na čtvrtém řádku a třetím sloupečku máme tlačítko Enter, pokud ovšem není aktivní modifikátor shift, v kterémžto případě tam máme tlačítko Clear. Metoda pouze vytvoří ButtonTuplet s parametry které jste dodali a přidá je do úložiště _registeredButtons.

Samotné modifikátory je ovšem také nutno registrovat. Modifikátory se ale mohou chovat různě. Například, klávesa Shift je obvykle aktivní pouze dokud ji nepustíte, Num Lock zase může být jedním stiskem zapnut, dalším vypnut. V nastavení usnadnění si na svém počítači můžete nastavit, aby klávesy jako Shift zůstaly zmáčknuté i když je pustíte až do doby, dokud nezmáčknete jinou standardní klávesu (tzv. funkce jedním prstem / sticky keys). Všechny tyto možnosti lze vyjádřit položkou z tohoto enumu:

namespace UAM.InformatiX.SPOT.Hardware { public enum ModifierBehavior { Normal, StickyKey, ToggleKey } }

Registrace modifikátoru pak probíhá obdobně jako u tlačítek:

KeypadInput.RegisterModifier(new MatrixKeyboard.KeyLocation(3, 0), ModifierKeys.Shift, ModifierBehavior.StickyKey);

Což znamená: chceme, aby klávesa v prvním sloupci a čtvrtém řádku fungovala jako Shit jedním prstem. Stejně tak jako v případě tlačítek, metoda uloží parametry do ModifierTuplet a ten přidá do seznamu _registeredModifiers.

Obsluha KeyDown a KeyUp událostí je pak celkem jednoduchá. Např. při KeyDown handler zkontroluje, zda je k dané KeyLocation (a aktuálnímu modifikátoru) přiřazené nějaké tlačítko a pokud ano, nahlásí událost ButtonDown. To samé dělá KeyUp handler pro událost ButtonUp. Rozdíl nastává ve zpracování modifikátorů - v KeyDown se nejdříve zkontroluje, zda je na daných souřadnicích registrován nějaký modifikátor. V takovém případě je přidán nebo odebrán ze seznamu _currentModifiers, podle toho, jak se má modifikátor chovat. Všechny dočasné modifikátory, což jsou momentálně pouze ty s funkcí jedním prstem, jsou ze seznamu odebrány při puštění některé standardní klávesy.

Samotné stisknutí modifikátoru není nikde hlášeno, pouze se jím ovlivňují výsledná tlačítka. Můžete se však přihlásit k události MatrixKeyboardInput.CurrentModifierChanged pokud potřebujete zobrazovat aktuální modifikátory v uživatelském rozhraní. Jsou k dispozici vlastností CurrentModifier, která je spojeným pohledem na seznam _currentModifiers. Mějte také na paměti, že běžné chování modifikátorů může nejen působit uživateli potíže s ovládáním takového zařízení, ale rovněž vyžaduje spolehlivé rozpoznávání současného stisku libovolných kláves, což může být u maticových klávesnic trochu problém - proto doporučuji užívat spíše sticky nebo toggle nastavení.

Až budete definovat všechny klávesy na klávesnici, mohly by se vám hodit ostatní přetížení registračních metod (které mají mimochodem také lepší výkon než přiávání tlačítek jedno po druhém). Přikládám kompletní registraci pro STD 34-07:

KeypadInput.RegisterModifier(new MatrixKeyboard.KeyLocation(3, 0), ModifierKeys.Shift, ModifierBehavior.StickyKey); KeypadInput.RegisterButton( // namapování souřadnic na tlačítka bez aktivního modifikátoru new MatrixKeyboard.KeyLocation[] { new MatrixKeyboard.KeyLocation(0, 0), new MatrixKeyboard.KeyLocation(0, 1), new MatrixKeyboard.KeyLocation(0, 2), new MatrixKeyboard.KeyLocation(1, 0), new MatrixKeyboard.KeyLocation(1, 1), new MatrixKeyboard.KeyLocation(1, 2), new MatrixKeyboard.KeyLocation(2, 0), new MatrixKeyboard.KeyLocation(2, 1), new MatrixKeyboard.KeyLocation(2, 2), /* SHIFT */ new MatrixKeyboard.KeyLocation(3, 1), new MatrixKeyboard.KeyLocation(3, 2) }, ModifierKeys.None, new Button[] { (Button)KeypadButton.NumPad7, (Button)KeypadButton.NumPad8, (Button)KeypadButton.NumPad9, (Button)KeypadButton.NumPad4, (Button)KeypadButton.NumPad5, (Button)KeypadButton.NumPad6, (Button)KeypadButton.NumPad1, (Button)KeypadButton.NumPad2, (Button)KeypadButton.NumPad3, /* SHIFT */ (Button)KeypadButton.NumPad0, (Button)KeypadButton.Enter, }); KeypadInput.RegisterButton( // namapování souřadnic na tlačítka při aktivním modifikátoru Shift new MatrixKeyboard.KeyLocation[] { new MatrixKeyboard.KeyLocation(0, 0), new MatrixKeyboard.KeyLocation(0, 1), new MatrixKeyboard.KeyLocation(0, 2), new MatrixKeyboard.KeyLocation(1, 0), new MatrixKeyboard.KeyLocation(1, 1), new MatrixKeyboard.KeyLocation(1, 2), new MatrixKeyboard.KeyLocation(2, 0), new MatrixKeyboard.KeyLocation(2, 1), new MatrixKeyboard.KeyLocation(2, 2), /* SHIFT */ new MatrixKeyboard.KeyLocation(3, 1), new MatrixKeyboard.KeyLocation(3, 2) }, ModifierKeys.Shift, // toto přetížení předpokládá, že všechna tlačítka jsou mapována pro stejný modifikátor new Button[] { (Button)KeypadButton.F4, (Button)KeypadButton.Up, (Button)KeypadButton.F5, (Button)KeypadButton.Left, (Button)KeypadButton.F3, (Button)KeypadButton.Right, (Button)KeypadButton.F1, (Button)KeypadButton.Down, (Button)KeypadButton.F2, /* SHIFT */ (Button)KeypadButton.Decimal, (Button)KeypadButton.Clear, }); Keypad.StartListening(); // nezpapomeňte pak ovladač spustit, bez toho to není taková zábava :)

Všechny třídy a enumy můžete stáhnout v jednom archivu zde.

Závěrečné poznámky:

Tak si to užijte!

Comments
Sign in using Live ID to be able to post comments.