25. Matriisinäppäimistö 2

Alunperin julkaistu: 12.2.2017

Viimeksi muokattu: torstai 11.6.2020

Edellisessä osassa tutkittiin, kuinka näppäimistöä voidaan lukea digitaalisia porttipinnejä hyödyntämällä. Tässä osassa keskitytään siihen, kuinka 16 eri näppäintä voidaan lukea vain yhtä porttipinniä hyödyntämällä.

Tarve kytkennälle tulee siitä, että usein IO-linjojen määrät ovat rajoitettuja ja käytettyjen linjojen määrä täytyy minimoida. Yksi vaihtoehto on tietenkin hankkia suuremman IO-määrän sisältävä mikrokontrolleri, mutta näin harrastajapuolella se on usein "huonompi" vaihtoehto joko taloudellisesta syystä tai pelkästään siitä, että haluaa toteuttaa jonkun projektin mahdollisimman nopeasti.

Osassa 17 ja 18 tutustuttiin mikrokontrollerin analogiamuuntimen käyttöön, joten tähän ei sen vuoksi tässä osassa palata. Muistin virkistykseksi voit silmäillä kyseiset kappaleet uudestaan, mikäli analoginen puoli ei tunnusta vielä vahvalta osaamisalueelta.

Tässä osassa toteutetaan analogisen näppäinmatriisin kalibrointi ja käyttöönotto kalibrointiarvojen avulla. Kalibrointitiedot tallennetaan erilliseen Flash-muistialueeseen, joka pysyy muuttumattomana vaikka itse pääohjelmaa muokkaisikin.

Tarvittavat osat

Tässä osassa tarvitset matriisinäppäimistön lisäksi 8 vastusta sekä johtimia (ja mahdollisesti myös koekytkentäalustan). 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 jokin muu matriisinäppäimistö
  • Vastuksia: 180, 680, 3300, 1500, 2 x 1000, 820 ja 560 ohmia.
  • Koekytkentäalusta

Analoginen kytkentä matriisinäppäimistölle

Analoginen kytkentä saadaan aikaiseksi jännitejakokytkennän avulla. Jännitteenjaolla on mahdollista saada periaatteessa mikä hyvänsä jännite aikaiseksi, mutta käytännössä meitä rajoittaa vain käytettävissä olevat vastusarvot. Alla on esitetty kytkentä, kuinka erilaisia jännitearvoja kullekin näppäimelle matriisissa saadaan tehtyä.

Kuten kytkennästä voidaan huomata, niin käytössä on 16-näppäimen matriisikytkentä ja tätä varten tarvitaan 8 kappaletta vastuksia. Ain kytketään mikrokontrollerin analogia-inputtiin ja vastukset R1-R8 valitaan sopivasti niin, että kullekin näppäimelle muodostuu oma yksittäinen "uniikki" jännite. Tämä vastusten jono muodostaa näin ollen jännitteenjakokytkennän, missä vastukset pudottavat jännitettä riippuen siitä mitä nappia painettiin. Tästä on kerrottu hieman tarkemmin lisää elektroniikkaosan sivulla: analoginen matriisi, mistä löydät myös Excel-laskurin vastusarvoille mikäli käsin laskeminen tunnustaa hankalalta.

Alle on listattu tässä esimerkissä käytetyt vastusarvot, jotka löytyvät myös alussa mainitusta osalistasta.

Vastus Ohmit
R1 180
R2 680
R3 3300
R4 1500
R5 1000
R6 820
R7 1000
R8 560

Eri vastusarvoille voit käyttää vastusarvo-laskuria, mikäli käsin laskeminen ei kiinnosta.

Analogisen jännitteen muodostuminen kytkentään

Tämän esimerkin mukaisessa tapauksessa jännitearvot ovat laskennallisesti seuraavat (keltainen taustaväri):

Nappi Jännite Nappi Jännite Nappi Jännite Nappi Jännite
X1Y1 '1' 0,32 X2Y1 = '2' 0,78 X3Y1 = '3' 1,07 X4Y1 = 'F' 1,35
X1Y2 '4' 0,43 X2Y2 = '5' 0,98 X3Y2 = '6' 1,31 X4Y2 = 'E' 1,61
X1Y3 '7' 1,42 X2Y3 = '8' 2,32 X3Y3 = '9' 2,64 X4Y3 = 'D' 2,87
X1Y4 'A' 2,72 X2Y4 = '0' 3,23 X3Y4 = 'B' 3,35 X4Y4 = 'C' 3,42

Yllä näkyvät ovat siis teoreettisia arvoja ja käytännössä todelliset arvot tarvitsee mitata ennakkoon omalle kytkennälle tai tehdä jonkinlainen kalibrointi ohjelma (kuten tulevassa esimerkissä). Arvojen epätarkkuus johtuu käytettävien vastuksien toleransseista, jonka vuoksi täysin tarkkoja arvoja on mahdoton etukäteen laskea (ellei mittaa kaikkia käytettyjä vastuksia).

Vaikka tarkkoja vastuksia käytettäisiinkin, niin joka tapauksessa analogisessa mittauksessa kalibrointi on yleensä tarpeellista (AD-muuntimessa sekä referenssissä on virhettä ja kytkennässä voi olla jotain mitä ei laskuissa ole osannut ottaa huomioon).

Yleismittarilla mitattuna jännitteissä on muutamien prosenttien heitto, joten näitä arvoja ei oikein tällaisenaan voi käyttää vaan tarvitaan kalibrointia, mistä lisää alempana. Arvot on kuitenkin hyvä tietää edes suurinpiirtein, sillä väärillä vastusarvoilla eri nappeihin muodostuu sama jännite ja sitähän tässä ei haluta.

Matriisinäppäimistön kytkeminen mikrokontrolleriin

Yllä olevasta kuvasta näet, kuinka näppäimistö voidaan kytkeä vaikkapa P1.3 tuloon MSP430G2231 mikrokontrollerille. Kytkennässä jää siis vielä vapaaksi pinnit P1.0 - P1.2. Tällä kertaa kytkennästä ei ole valokuvaa, sillä kytkentä muistuttaa paljon edellisessä osassa esiteltyä kuvaa. Kytkentään olisi hyvä lisätä vielä 100 nF:n kondensaattori Ain-tulon ja maan väliin, mutta tässä se on jätetty pois (laiskotti ;)).

Näytteistysajan määrittäminen tsample

Kytkentäkuvasta voidaan huomata, että "pahimmassa tapauksessa" Ain-pinnissä näkyy jännitejaon jälkeen 3 sarjavastusta (R7, R6 ja R5, koska käyttäjä painaa sarakkeen X1 jotain nappulaa). Nämä vaikuttavat AD-muuntimen ominaisuuksien takia näytteistysaikaan ja sen vuoksi on hyvä tietää miten nopeasti näytteitä voidaan näppäimistöltä lukea.

Mikrokontrollerin käyttöoppaasta (User's Guide, SLAU144) nähdään sivulta 552 näytteistysaikaan vaikuttavat tekijät ja kaava millä tähän kytkentään sopiva näytteistysaika voidaan laskea, jolloin saadaan:

tsample > (RS + RI) * ln(211) * CI
RI = 2kohm, CI = 27 pF, ln(211) = 7,625, Rs = R7 + R6 + R5 = 2820
tsample > (2,82 kohm + 2kohm) * 7,625 * 27pF
tsample > 0,993 us
tsample < 1 007 741 Hz

Kuten lasku osoittaa, niin näytteitä voidaan ottaa tästä matriisista enimmillään noin  megahertsin taajuudella tai noin 1 mikrosekunnin välein. MSP430G2231 mikrokontrollerin näytteistykselle tämä ei ole ongelma, sillä sisäisesti mikrokontrolleri ottaa vähintään neljän kellojakson verran näytteitä signaalista.

Seuraavaksi täytyy määrittää ohjelmassa tiettyjä raja-arvoja ja tehdä näppäimistön kalibrointi.

Esimerkki 1: Matriisinäppäimistön kalibrointi

Jotta matriisinäppäimistöä voidaan käyttää, täytyy se ensin kalibroida. Kalibrointi tehdään debuggerin avulla ja kalibroidut tiedot tallennetaan INFOC-nimiseen flash-muistialueeseen. Tämä olisi mahdollista toteuttaa myös niin, että debuggerin avulla katsottaisiin jokaisen napin antama AD-muunninarvo ja nämä muunninarvot tallennettaisiin johonkin staattiseen taulukkoon ja verrattaisiin siihen ajon aikana. No, periaatteessa tässä on kyse samasta asiasta, mutta minun tavalla tehtynä arvoja ei tarvitse kirjata erikseen mihinkään ylös ja kirjoittaa siitä edelleen lähdekoodiin oikeille riveille.

Flash-muistiin tallennusta ei tässä oppaassa ole tarkemmin käyty läpi tähän mennessä, mutta asia ei ole kovin erikoinen kaikkineensa. Tarkemman selityksen jätän johonkin myöhempään osaan, sillä tämä osa keskittyy matriisinäppäimistön lukuun analogisesti.

Edellisten asioiden lisäksi, tässä osassa ei näytetä koko lähdekoodia, sillä se on hyvinkin pitkä tähän esitettäväksi (lähemmäs 300 riviä). Tällä kertaa esittelen vain mielestäni olennaisimmat osat ja tarkemman koodin kommentteineen voit ladata alta löytyvästä zip-paketista.

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

Suosittelen tallentamaan projektin omaan työkansioosi ja kääntämään sen omassa ympäristössäsi. Ohjelma on asetettu valmiiksi kalibrointimoodiin, joten sinun tarvitsee vain asettaa breakpointit ja toimia alla olevan ohjeen mukaisesti. Koodia on pyritty kommentoimaan mahdollisimman kattavasti, jotta ohjelman toiminta olisi helppo ymmärtää.

Seuraavassa selitän ensin kalibroinnista ja sen käyttämisestä ja sen jälkeen selostan varsinaisen ohjelman toimintaa.

Kalibrointiohjelma

Kun tarkastelet lähdekoodia, niin huomaat, että rivillä 29 on määritys #define CALIBRATION. Tämä define määrittelee käytetäänkö kalibrointiohjelmaa vai tavallista luku ja numeron tulostusohjelmaa. Tämä määritys toimii yhdessä #ifdef, #ifndef ja #endif sanojen kanssa. Näillä kääntäjälle varatuilla sanoilla voidaan samaan ohjelmaan tehdä kaksi tai useampi eri vaihtoehto käännöksille. Tällä tavoin suuria osia koodista ei tarvitse erikseen kommentoida, sillä ne joko otetaan mukaan tai jätetään pois käännöksestä sen mukaan kuin on tarpeen. Tarkemman käytön näistä kääntäjän ohjausrakenteista näet lähdekoodista.

Taulukko, mihin kalibrointitiedot tallennetaan on globaali keyValues -niminen taulukko. Tämä alustetaan memset-funktiolla rivillä 90. Memset on C-standardin määrittelemä funktio, millä voidaan alustaa taulukoita mihin tahansa haluttuun arvoon. Memset-funktion syntaksi on, kuten lähdekoodistakin huomaat:

memset( muuttuja, alkuarvo (8-bit numero/kirjain), taulukon_koko);

Muuttujan tyyppinä funktiossa on (void *), joka kertoo että kyseessä on osoitin muuttujaan. Osoittimista ei kannata tässä vaiheessa välittää vaan siirtyä eteenpäin. Alkuarvona koodissa on 0, mutta se voisi olla mikä hyvänsä luku 0 - 255 välillä. Myös siis kirjaimet käyvät esim 'a' tai 'o', kunhan ne ovat ASCII-merkistöä ja siis numeroita 0 - 255 välillä. Taulukon koko on selvitetty sizeof()-funktiolla, joka on käännöksen aikainen funktio, joka välittää funktion parametriksi taulukon koon numeerisena arvona. Sizeof-funktiota ei siis suoriteta ohjelman suorituksen aikana, toisin kuinmemset-funktiota.

Taulukon alustuksen jälkeen alustetaan normaalisti muut "härvelit" jonka jälkeen siirrytään ohjelman pääsilmukassa itse kalibrointiin, alkaen riviltä 100.

        .
        .
        .
       #ifdef CALIBRATION
              for(index = 0; index < MAX_KEYBOARD_SYMBOLS; index++)
              {
                     // .... kommentit lähdekoodissa ....
                     __no_operation(); // <-- breakpoint tähän
                     StartAdc();
                     WaitAdc();
                     keyValues[index] = ReadAdcValue();
              }
         .
         .
         .

Kalibrointi tehdään pääohjelmassa debuggerin avulla niin, että riville missä on käsky __no_operation(); asetetaan breakpoint. Ohjelma ajetaan tähän kyseiseen breakpointtiin ensimmäisen kerran, jonka jälkeen matriisinäppäimistöstä painetaan numero 1 pohjaan ja pidetään se pohjassa. Tämän jälkeen hiirellä klikataan vihreää Run/Resume nuolta (alla kuvassa painike 1).

Nyt ohjelma hakee AD-muunnoksen ja tallentaa sen väliaikaiseen globaaliin taulukkoon keyValues (väliaikainen koska sitä ei tarvita toimivassa ohjelmassa). Kaikki 16 nappia käydään tällä tavalla läpi ja kun kaikki on mitattu, arvot tallennetaan flash-muistiin WriteCalibrationDataToFlash() -funktiolla. Mikäli matriisinäppäimistön kytkintä ei paineta, mutta esimerkiksi vahingossa painetaan Run/Resume -nappia, ei ohjelma tallenna sen hetkistä arvoa taulukkoon. Tämä on tehty tarkastelemalla saatua AD-muuntimen arvoa rivillä 114.

Alla olevassa kuvassa on debuggerin ikkunasta kuvakaappaus, mistä nähdään kaikki 16:n napin AD-muunninarvot, jotka itse sain Launhpadille ja kytkennälle. Kun viimeinenkin nappi on kalibroitu, niin ohjelman suoritus "pysähtyy" while(1); silmukkaan riville 132. Mikäli tällä rivillä ei ole breakpointtia, täytyy ohjelman suoritus pysäyttää "pause" napilla (2:n vasemmalla puolella kuvassa ylhäällä).

Kun WriteCalibrationDataToFlash() -funktio on suoritettu, keyValues -taulukko pitäisi olla tallennettuna INFOC-muistiin (osoite 0x1040). Muisti-ikkunasta (Memory Browser) voit tarkastaa tallentuivatko tiedot sinne. Mikäli ei, niin varmista että et ole tehnyt muutoksia esimerkkikoodiin ennen kaikkea WriteCalibrationDataToFlash() -funktioon. Alla olevasta kuvasta nähdään miten tiedot tallentuivat kyseiseen lohkoon. Huomaathan, että Expressions -ikkunan muuttuja sijaitsee RAM-muistissa (osoite 0x2000) ja alla olevat tiedot Flash-muistissa (osoite 0x1040)!

Osoitteista ja flash-muisteista kerron lisää joskus myöhemmin toisessa osassa.

Kun olet saanut yllä olevan tehtyä ja tarkistettua, voit lopettaa debuggerin käytön (nappi 2 yllä olevassa kuvassa) ja siirtyä eteenpäin.

Kalibrointitietojen käyttö

Seuraavaksi tehdään esimerkkikoodiin muutos, joka sallii meidän käyttää kalibrointiarvoja varsinaisessa ohjelmassa.

Rivin 29 alkuun laitetaan nyt siis kommentti, joka poistaa kalibrointiin tarvitut funktiot käännöksestä (eli kommentoidaan #define CALIBRATION rivi). Näin ollen FLASH-muistissa olevia jo tallennettuja tietoja voidaan hyödyntää näppäimen luvussa.

Klikataan hiiren toisella napilla projektikansion päältä ja valitaan "Clean project", jolloin kaikki entiset määritykset ja käännökset poistuvat. Tämän jälkeen valitaan samasta valikosta "Rebuild project". Tämän jälkeen tarkistetaan vielä Debug -asetuksista, että "Erase main memory only" asetus on valittuna.

Edellä mainittu clean-rebuild on tärkeää, koska muuten voi käydä niin että kalibrointitiedot häviävät INFO-muistista kun uutta ohjelmaa ladataan mikrokontrollerille - vaikka "Erase main memory only" asetus on valittuna. (Huomasin tämän tätä esimerkkiä tehdessä kun kalibrointitiedot hävisivät jatkuvasti muistista vaihdellessani kalibrointimoodin ja tavallisen toimintamoodin välillä).

Esimerkki 2: Näppäimistön lukeminen AD-muuntimella

Kuten edellä mainittiin, niin tämän esimerkin toimintaan vaikuttaa kalibroinnin tekeminen. Mikäli sitä ei ole tehty, ei ohjelma toimi oikein.

Ohjelma käynnistyy alustusten jälkeen silmukkaan, missä käynnistetään muunnoksen ottaminen, odotetaan sen valmistumista ja välitetään saatu muunnostietoReadKeyDataFromFlash() -funktiolle. Kyseinen funktio tutkii oliko saatu arvo tasan yhtäsuuri tai tietyn rajan sisällä verrattuna kalibrointitietoihin ja palauttaa globaalista symbolitaulukosta oikean symbolin (eli numeron), joka voidaan edelleen välittää SetSegmentNumber() -funktiolle.

Rivillä 142 näet, kuinka funktion parametrina voi käyttää myös toista funktiota, mutta tällöin kannattaa tarkastaa että parametrifunktio palauttaa saman tyyppisen arvon, kuin mikä parametrinä pitää olla (unsigned int <-> unsigned int tässä tapauksessa).

Mikäli napin painallus (tai oikeastaan AD-muunnos oli validi), niin näytölle asetetaan mittaustietoa vastaava numero.

Koodia voit jälleen vapaasti muokata ja tutkia kuinka se toimii. Lisäominaisuuksia saa tähänkin ohjelmaan tehtyä edellisten osien ja koodiesimerkkien avulla.