24. Matriisinäppäimistö

Alunperin julkaistu: 12.2.2017

Viimeksi muokattu: torstai 11.6.2020

Edellisessä osassa perehdyttiin lukujen tulostamiseen, mutta usein lukuja tarvitsee myöskin lukea käyttäjältä, eikä aina pelkästään yksi tai kaksi nappia riitä tähän tarkoitukseen. Esimerkiksi jos ajatellaan, että täytyy rakentaa koodilukko, joka vastaanottaa nelinumeroisen koodin ja oikean koodin kohdalla avaa lukon, niin tällöin kätevintä on käyttää jonkin sortin näppäimistöä.

Näppäimistöjä on toki monenlaisia, mutta keskitytään tässä osassa vain 4x4 matriisinäppäimistön toimintaan ja sen käyttöönottoon. Edellisen osan komponenttien lisäksi tarvitset tätä esimerkkiä varten matriisinäppäimistön (sellaisen voi toki itsekin tehdä) ja johtimia.

Matriisinäppäimistö ja sen toiminta

Tässä osassa käytetään siis näppäinmatriisia, joka koostuu 16 yksittäisestä kytkimestä. Tällainen kytkinmatriisi on toteutettu 4 x 4 kytkinmuodostelmalla ja näin ollen sen lukemiseen tarvitaan 4 + 4 IO-linjaa. Käytettävä matriisinäppäimistö on tyypiltään ECO 16250, mistä on kuva alla:

Edellisen osan esimerkkiin lisätään siis yllä oleva näppäimistö. Mutta kuinka tällaisen näppäinmatriisin lukeminen sitten onnistuu? Vastaus on yksinkertaisesti pollaamalla tai skannaamalla, eli tarkkailemalla X tai Y linjojen tilaa. Mutta hetkinen hetkinen, siis mitkä X ja Y linjat? Otetaanpa rauhallisemmin.

Koska kyseessä on matriisinäppäimistö, on näppäimet asetettu (pysty ja vaaka) X ja Y riveihin alla olevan kuvan mukaisesti:

Kuten kuvasta voidaan huomata, numero 1 vastaa Y1:X1 "koordinaattia", numero 2 vastaa Y1:X2 ja niin edelleen. Kun näppäintä 1 painetaan, linjat Y1 ja X1 yhdistyvät fyysisesti ja sen voit testata esimerkiksi yleismittarin piipparilla. Nyt jos Y1 linjaan on johdettu jännitettä, esimerkiksi 3,3 volttia, niin X1 linjassa on myöskin 3,3 volttia sillä napin painallus on yhdistänyt kyseiset kontaktit. Vastaava tapahtuu myös numeroa 2 painamalla, mutta tällöin 3,3 volttia voidaan mitata linjasta X2. Sama logiikka toistuu läpi näppäinmatriisin.

Käytetyn näppäinmatriisin pinnijärjestys takaapäin katsottuna on: Y4, Y3, Y2, Y1, X4, X3, X2, X1. Mikäli sinulla on käytössä erilainen näppäinmatriisi, niin voit tarkistaa kytkennän yleismittarin piipparin avulla.

Näppäimen painalluksen havaitseminen

Periaate näppäimen painamisen havaitsemiseksi on edellä tulleiden asioiden vuoksi yksinkertainen. Kun yhteen X tai Y linjaan johdetaan sähköä (eli asetetaan linja "1"-tilaan), niin muita aktiivisen linjan kanssa ristikkäisiä linjoja tutkimalla voidaan havaita painallus. Alla oleva GIF-kuva havainnollistaa tämän toimintaa. (kuvan lähde)

Tarvittavat osat

Tätä kytkentää varten tarvitaan vastuksia, johtimia, koekytkentäalusta ja näytönohjainpiiri sekä matriisinäppäimistö. Alla näet osalistan kokonaisuudessaan:

  • 7-segmenttinäyttö (GL8E040)
  • HCF4511 (BE) BCD-7segmentti dekooderi
  • 7 kpl 220 ohmin vastuksia
  • Johtimia
  • Launchpad + MSP430G2231 mikrokontrolleri
  • ECO 16250 Matriisinäppäimistö tai muu matriisinäppäimistö
  • Koekytkentäalusta

Matriisinäppäimistön kytkeminen mikrokontrolleriin

Nyt kun matriisin toiminta on selvillä, voidaan rakentaa edelliseen esimerkkiin pohjautuva kytkentä, mistä kytkentäkuva onkin alla:

Kuten näkyy, niin kytkentää tarvitsee muuttaa vain hieman. LE-nasta on ohjainpiiriltä kytketty suoraan maahan, jolloin numeroa ei voida lukea näytöltä eikä sitä voi näin ollen lukita. Tämä ei kuitenkaan haittaa kuten jatkossa tulet huomaamaan. Näppäinmatriisin Y-linjat on kytketty MSP430G2231 mikrokontrollerin P1.4 - P1.7 linjoihin, jotka samalla ohjaavat näytön toimintaa - aivan kuten edellisessä osassa. Porttipinneihin P1.0 - P1.3 on kytketty näppäimistön X-linja, jolloin näitä linjoja tutkimalla voidaan havaita näppäimen painallus.

Alla olevassa kuvassa nähdään kytkentähässäkkä joka on rakennettu tätä esimerkkiä varten:

Esimerkki 1: Näppäimistön lukeminen

Tässä esimerkissä edellisen osan koodeihin luodaan näppäinten lukufunktio ja edellistä esimerkkiä tarvitsee muuttaa hieman mm. porttien alustusten osalta. Koodi on kokonaisuudessaan alla:

#include <msp430g2231.h>
// Ohjelmamuistin koko tavuina (byte) Flash/RAM = 342/18

#define LED_RED                      (BIT0)
#define LED_GREEN                   (BIT6)
#define INIT_LAUNCHPAD_LEDS  (P1DIR |= LED_RED+LED_GREEN)
#define LED_OFF                       (P1OUT &= ~(LED_RED + LED_GREEN))

// 7-segmentti määritykset
#define SEGMENTPORT    (P1OUT)
#define SEGMENTDIR       (P1DIR) 
#define SEGMENTPINS      (BIT7 + BIT6 + BIT5 + BIT4) 

// Matriisinäppäimistön määritykset
#define X1  BIT0
//#define X2  BIT1
//#define X3  BIT2
//#define X4  BIT3
#define Y1  BIT4
//#define Y2  BIT5  // kommentoituja määrityksiä ei tarvita, 
//#define Y3  BIT6  // mutta ne on kytkennän havainnollistamisen
//#define Y4  BIT7  // vuoksi jätetty tähän

// Launchpad funktiot
inline void InitLaunchpad();

// 7-Segmenttifunktiot
void InitSegmentDriver();
void SetSegmentNumber(unsigned char n);

// Matriisinäppäinfunktiot
void InitKeyboardInputs();
unsigned char ScanKeyboard();

void main(void)
{
      unsigned char key=0;
      WDTCTL = WDTPW + WDTHOLD;    // watchdog pysäytys
      InitLaunchpad();
      InitSegmentDriver();
      while(1)
      {
           __delay_cycles(100000); 
           // 1 MHz / 0,1 MHz = 10 Hz skannaustaajuus
           if(key != 0xFF)
           {
                 SetSegmentNumber(key);
           }
           else
           {
                 SetSegmentNumber(0xFF);
           } 
           __no_operation(); // aseta breakpoint tähän
      } 
} 

// Launchpad alustusfunktio
inline void InitLaunchpad()
{
     INIT_LAUNCHPAD_LEDS; // Ledien alustus
     LED_OFF;  // LED1 ja LED2 pois päältä
     P1DIR = 0xFF;
     P1IE = 0;
     P1IES = 0;
     P1REN = 0;
     P1OUT = 0;
     // alustetaan CPU:n kello 1 Mhz
     BCSCTL1 = CALBC1_1MHZ;
     DCOCTL = CALDCO_1MHZ;
     // BCSCTL3 = XCAP_3;
     // kiteen alustus on kommentoitu koska sitä ei nyt tarvita
}

// Segmentin alustusfunktio 
void InitSegmentDriver()
{
     // asetetaan pinnit lähdöksi ja nollataan kaikki 
     SEGMENTDIR |= SEGMENTPINS + LE_PIN; 
     // asetetaan segmenttipinnit "1" tilaan, jolloin näytössä 
     // ei näy mitään. Jos porttipinnit on "0" niin 0 näkyy näytössä.
     SEGMENTPORT |= SEGMENTPINS; 
}

// Segmentin numeron asetusfunktio. 
// Parametrina haluttu numero (0...9 mahdolliset näyttää)
void SetSegmentNumber(unsigned char n)
{
     // siirretään numero (joka mahdollisesti on 0 - 9)
     // ylempään 4 bittiin 
     n <<= 4;
     SEGMENTPORT = 0xF0 & n;  // maskaus ja porttibittien asetus
}

// Näppäimistön alustusfunktio, asetetaan X-linjan 
// porttipinnit lukutilaan ja alasvetovastukset ko. linjoille
void InitKeyboardInputs()
{
     // Outputit Y ja asetetaan X-linjat lukutilaan
     P1DIR = 0xF0;
     // alasvetovastukset ettei inputit 'kellu'
     P1REN = 0x0F;
}

// Defineitä voi tehdä myös "kesken kaiken"
#define MAX_COLUMNS      4  // Matriisin sarakkeet eli x-akseli
#define MAX_ROWS           4  // Matriisin rivit eli y-akseli

// Näppäimistömatriisin skannausfunktio 
// Tämä funktio palauttaa painetun numeron tai kirjaimen. 
// Mikäli mitään ei ole painettu, niin palautetaan 0xFF 
unsigned char ScanKeyboard()
{
     unsigned char scan_Y = 0, scan_X = 0;
     unsigned char y = 0, x = 0;
     unsigned char symbols[4][4] = {    // näppäimistön määritys
                                                       {1, 2, 3, 'F'},
                                                       {4, 5, 6, 'E'},
                                                       {7, 8, 9, 'D'},
                                                       {'A',0, 'B', 'C'},
                                                  };
     // alustetaan pinnit ennen skannausta,
     // jolloin P1.0 - P1.3 pinnejä voidaan käyttää myös outputtina
     // kun tätä funktiota ei suoriteta
     InitKeyboardInputs();
     
     // Asetetaan Y1-linja "1" tilaan ja skannataan X1...X4
     scan_Y = Y1;
     for(y = 0; y < MAX_ROWS; y++)
     {
          // asetetaan Y1 ... Y4 "1" tilaan yksi kerrallaan
          // ja luetaan X1 ... X4
          scan_X = X1;
          P1OUT = scan_Y;
          for(x = 0; x < MAX_COLUMNS; x++)
          {
               if(!(P1IN & 0x0F))
               {
                    // mikäli yksikään X-linja ei ole "1" -tilassa,
                    // voidaan silmukasta poistua. 
                    break;
               }
               // luetaan X linjat yksitellen, jos jokin X-linja on 
               // myös "1" tilassa niin tiedämme että kyseinen Xn-linja
               // on aktiivinen. x-muuttuja kertoo meille mikä linja on 
               // aktiivinen ja sitä kautta saadaan symbols-
               // taulukosta matriisin "symboli" eli numero selville 
               if(P1IN & scan_X)
               {
                    // jos näppäintä painettu, lopetetaan skannaus
                    // ja tutkitaan mikä nappi oli ja palautetaan sen arvo
                    // luodusta symboli-taulukosta
                    return symbols[y][x];
                    // taulukko voi olla myös globaali, jolloin
                    // näppäimen merkityksiä voidaan ennen tämän
                    // funktion kutsumista vaihtaa (esim. pienet/isot 
                    // kirjaimet jne) 
               }
               // siirretään X-linjan tarkkailua kohti P1.3:sta
               scan_X <<= 1;
          }
          // siirretään asetettua "skannausbittiä" vasemmalle
          scan_Y <<= 1; 
     }
     // mitään nappia ei oltu painettu koska funktion suoritus
     // pääsi tänne asti, palautetaan FF ja segmenttinäytön vilkkumisen
     // pienentämiseksi asetetaan P1OUT 0xF0 tilaan.
     P1OUT = 0xF0;
     return 0xFF;
}

Lataa tämän esimerkin CCSv5 projekti tästä.

Ohjelman toiminta

Ohjelmassa alustetaan ensin mikrokontrolleri toimimaan launchpadilla 1 MHz:n nopeudella. Tämän jälkeen alustetaan 7-segmenttinäytön ohjainpiiriä ohjaavat porttipinnit. Ohjelman pääsilmukassa odotetaan noin 100 millisekuntia jonka jälkeen kutsutaan ScanKeyboard() -funktiota, mikä tutkii onko näppäimiä painettu ja palauttaa painetun näppäimen. Tämän jälkeen näppäin asetetaan näytölle SetSegmentNumber(..) -funktiolla.

ScanKeyboard() -funktio toimii siten, että kykin Y-linja matriisinäppäimistöstä asetetaan yksi kerrallaan "1"-tilaan, jolloin linjoissa P1.4-P1.7 vilisee vuoron perään loogista ykköstä ja nollaa. Samaan aikaan kun yksi Y-linjoista on "1"-tilassa, niin kutakin X-linjaa tutkitaan yksi kerrallaan.

Ehtolauseessa if(!(P1IN & 0x0F)) tutkitaan ensin onko mitään ylipäätään painettu ja jos on niin ohjelma siirtyy tarkemmin tutkimaan mikä itse asiassa oli se nappi mitä painettiin. Tämä tehdään lukemalla portin P1 input-rekisteri P1IN ja tekemällä sille AND-operaatio muuttujan scan_X kanssa. AND-operaatiohan antaa vastauksesi "1"-tilan vain ja ainoastaan jos kaikki "ändättävät" ovat "1" tilassa. Tätä varten tarvitaan siis scan_X muuttuja. Alla on esitetty kuinka näppäimen painallus ja painamattomuus havaitaan tämän muuttujan avulla:

Käyttäjä ei paina mitään:
scan_X = 0x01 = 0000 0001 (skannaus kuvitteellisesti portissa P1.0)
P1IN    = 0x10 =  0001 0000 (Y1-linja aktiivisena)
AND-operaatio P1IN <AND> scan_X = 0000 0000

Käyttäjä painaa nappia 1 (Y1 ja X1 matriisissa):
scan_X = 0x01 = 0000 0001 (skannaus kuvitteellisesti portissa P1.0)
P1IN    = 0x11 =  0001 0001 (Y1-linja aktiivisena, napin painallus yhdistää Y1:n ja X1:n)
AND-operaatio P1IN <AND> scan_X = 0000 0001

Apumuuttujina käytetään muuttujia y ja x, jotka kasvavat samassa tahdissa, kuin mitä matriisia käydään läpi. Näin ollen käyttäjän painallus voidaan suoraan kaapata koordinaateiksi, jotka kertovat näppäimen painalluksen sijainnin. Koska matriisinäppäimistön "symbolit" taikka numerot ja kirjaimet tiedetään etukäteen, voidaan koordinaatistoa hyödyntämällä poimia symbols-taulukosta käyttäjän painama kirjain.

Mikäli mitään nappia ei paineta, niin palautetaan 0xFF joka merkitsee että näppäimistöön ei ole koskettu. Tällöin näyttö pidetään tyhjänä kirjoittamalla sille 0xFF (koska 7-segmenttiohjain näyttää tyhjää kun kaikki A-D linjat ovat "1" tilassa).

Phuuhh. Toivottavasti selitys uppoaa, mutta jos ei mene jakeluun niin kannattaa tutkia koodia rivi riviltä ja lukea selitykseni uudestaan. Ennen pitkää ohjelman toiminta viimeistään aukeaa.

Alla olevassa videossa esitetään ohjelman toiminta käytännössä. Videosta voi huomata kuinka näyttö himmeästi hieman vilkkuu ja jos ihmettelet mistä tämä johtuu, niin se johtuu juurikin Y1-Y4 linjojen asettamisesta "1"-tilaan 10 kertaa sekunnissa (ja jos muistat että Y-linjat ohjasivat näyttöä niiiiiin... 😉 )

Tutki rauhassa koodia ja koita vaikka keksiä miten saisit himmeän vilkkumisen loppumaan tai miten pystyisit pienentämään pääohjelman koodirivien määrää (vinkkinä: palautusarvo, skannausjärjestys). Lisäksi voit yhdistellä edellisten osien koodeja ja tehdä tästäkin sovelluksesta mielenkiintoisemman.