perjantai 13. tammikuuta 2017

Koodilukko

Hiukan erilainen koodilukko. Hiukan erilainen on myös ohjelman rakenne. Tässä on pyritty mahdollisimman pieneen pääohjelmaan, mahdollisimman vähiin yleisiin muuttujiin (global variable) sekä keskittämään kaikki toiminnot aliohjelmiin. Tämä on se suunta, mihin ohjelmoinnin rakenteessa pitääkin pyrkiä. Tästä on se etu, että voi kopioida helposti jo aiemmin tehtyjä ohjelmapätkiä ja testattuja toimintoja. Tämä nopeuttaa ja helpottaa ohjelman kirjoittamista sekä parantaa ohjeman luotettavuutta. Kaikilla mitaleilla on myös se toinen puoli. Tässä se on se, että dokumentointi ja versionhallinta nousee hyvin tärkeään rooliin. Korostan tätä samalla ennen kaikkea itselleni, sillä ohjelmaa tehdessä ja testatessa sitä kuvittelee muistavansa miten se toimii, mutta muutaman kuukauden kuluttua on jo huuli pyöreänä.
 
Tässä ohjelmassa paras esimerkki tästä ja tulevasta käytännöstä on digitaali-tulojen ja -lähtöjen käsittely (boolean Fun_IO(int mode, int pinni, boolean tila){). Tuleville blogisivuille en tule enää kirjoittamaan tätä aliohjelmaa, vaan ainostaan viittauksen: IO_AliOhjelma_v1 ja mistä se on poimittavissa: IO_Ali_v1.No2Tam17. Tuossa viittauksessa on aliohjelman otsikko, sekä sivujen päivityksen numero kuukausittain ja vuosittain. Tässä aliohjemassa parametri mode määrittelee sen, onko kyse 1 = tulosta (INPUT), 2 = lähdöstä (OUTPUT), vai Arduinon sisäisesti 3 = ylösvedetystä (INPUT_PULLUP, ei tarvita ulkoista vastusta) tulosta. Toinen parametri: pinni määrittää IO-liitynnän. Kolmas parametri on lähdön ohjaus päälle tai pois. Jos kysytään tuloa, on tämä parametri aliohjemakutsussa nolla (0).
 
Tässä soveluksessa on käytössä 7-segmenttinäyttö, joka aiemmin on ollut noppa-sovelluksessa. Sen kytkentä on nähtävissä siis: ”Arpakuutio”.No2Kes16.
 
Ensimmäinen lause oli:”Hiukan erilainen koodilukko.” Erilaisuus johtuu siitä, että ”lukkoon” ei syötetä numerokoodia, vaan annetaan laitteelle palautetta siitä, onko ”koodilukon” arpoma numero oikea vai väärä. Kuuluuko arvottu numero (1 .. 9) avauskoodiin (tässä 7, 3, 5, 2) vai ei. ”Oven avaus” käynnistyy siitä, että painetaan molempia painikkeita samanaikaisesti. Kun painikkeet on vapautettu, pyörähtää näytön ledit kolme kertaa. Pienen tauon jälkeen näyttöön ilmestyy numero, jolloin pitää valita, onko se oikea (oikea painike) vai väärä (vasen painike). Ohjelma laskee oikeiden painamisten summan ja väärien painamisten summan. Jos molemmat täsmää,”aukeaa ovi” eli vihreä LED palaa hetken. Jos syöte on virheelinen, tai ei ole ehditty painaa ollenkaan, alkaa punainen LED vilkkumaan. Kun nämä palautteet ovat kuluneet ajallisesti loppuun (näiden aikana ohjema ei reagoi painamisiin), voidaan ”astua sisään”, tai yrittää uudestaan.
 
Voisi sanoa, että laitteen arpoma lukusarja on aina erilainen. Se ei pidä paikkaansa, mutta vaihtoehtoja on 362.880 kappaletta. Eli rosvo, joka kyttää nurkan takana, on luultavasti ihmeissään, edellyttäen, että hän ei näe, painetaanko vasenta vai oikeaa painiketta. Jos oletetaan, että joka kerta tulee eri lukusarja (totta kai voi tulla samojakin), ja sisään astutaan kerran päivässä, niin kestäisi 994 vuotta, kun väkisinkin tulisi sama lukusarja. Tätä ei välttämättä näkisi edes Metusalem, sillä hän – kertoman mukaan – eli ainoastaan 969 vuotta.
     Tämän koodilukon varmuutta voisi lisätä vielä siten, että oikea koodi olisi muuttuva esim. päivämäärän mukaan. Tarpeetonta kuitenkin tehdä turhan mutkikkaaksi, sillä ei tässä tuo lukko ole pääasia, vaan nuo ohjelmapätkät.
 
Pääohjema
Pääohjelma koostuu seitsemästä (7) askeleesta. Arduinon käynnistyessä ohjelma odottaa askelleessa yksi (1) molempien painikkeiden samanaikaista painamista. Kun ohjelma tunnistaa molemmat painikkeet, siirrytään askeleeseen kaksi (2) odottamaan painikkeiden vapautumista. Askeleessa kolme (3) pyöräytetään kolme kertaa 7-segmenttinäytön äärimmäisiä ledejä (A .. F, ei keskimmästä eli G). Pienen tauon jälkeen ohjelma hyppää askeleeseen neljä (4). Siinä kirjoitetaan nollaa näytön ohjainpiiriin (74HC595E) sekä täytetään taulukko Arr_Numerot[9]; arvotuilla numeroilla 1 .. 9. Askeleessa neljä (4) tehdään aliohjelmakutsu Fun_Arvonta(){ ja huolehditaan siitä, että taulukkoon tulee kukin numero vain kerran. Askeleessa viisi (5) näytetään 7-segmenttinäytössä kutakin arvottua numeroa pari sekuntia siinä järjestyksessä, missä ne arvottiin. Samalla tutkitaan onko jompaa kumpaa painiketta painettu. Valinta ja painaminen pitää tapahtua numeron näytön aikana. Lopuksi tarkastetaan oikeiden numeroiden summa (if(Int_OikeaSumma == 17 && Int_VaaraSumma == 28){).Mikäli molemmat summat täsmäävät, siirrytään askeleeseen kuusi (6), missä poltetaan hetken aikaa vihreää LEDiä. Mikäli summissa on virhe, tai jonkin numeron kohdalla on jäänyt painamatta, hyppää ohjelma askeleeseen seitsemän (7), missä vilkutetaan punaisata LEDiä. Näiden askelien viiveen kuluttua nollataan summat ja hypätään askelkeeseen yksi (1) odottamaan uutta yritystä.
 
Aliohjemat
Ensimmäisenä on viiveen aliohjelma: int Fun_Viive(int Viive){ . Toisin kuin viive delay(ms) ohjeman suoritus ei keskeydy viiveen ajaksi Tässä aliohjelmassa ainoastaan päivitetään viivelaskuria. Seuraavana on digitaalitulojen ja -lähtöjen käsittely boolean Fun_IO(int mode, int pinni, boolean tila){. Tässä aliohjelmassa määritellään dynaamisesti pinni tuloksi tai lähdöksi, eikä asetuksissa kuten tähän saakka. Kolmantena ja neljäntenä lasketaan oikeiden ja väärien numeroiden summat. Viidentenä on 7-segmenttinäytön tyhjennys void Fun_Tyhjenna(){. Toisin sanoen sarjamuodossa kirjoitetaan nollaa ohjauspiiriin ja siirretään näyttöön. Tämän osan muuttujamäärittelyitä on vielä asetuksissa, koska tämä on kopio ennen käytetystä ohjelmasta. Kuudentena on varsinainen näytön käsittelyn aliohjelma void Fun_Sijoita(int data){. Siinä määritellään bittimuodossa näytön reunat sekä numerot 1 .. 9. Kullekin tapahtumalle on oma sekvenssiaskeleensa. Kahta edellistä ohjelmaa kutsutaan seitsemännestä ohjelmapätkästä void Fun_Naytto(int j){. Tähän funktioon välitetään parametri j (vastaten näytettävää numeroa), mikä määrää suoritettavan askeleen sijoita-aliohjelmassa. Viimeisenä on arvonnan funktio void Fun_Arvonta(){. Siinä arvontaa suoritetaan niin kauan, että kukin numero saadaan sijoitettua taulukkoon int Arr_Numerot[9];. Asetuksissa on vielä muutama pinnimäärittely, mitkä jatkossa siirtyvät omiin aliohjelmiinsa. Sen lisäksi alustetaan sattumageneraattori vapaana olevasta analogiatulosta A5 randomSeed(analogRead(5));. Tässä tyhjennetää myös numerotaulukko.

 OHJELMA 33
 /***************************************
* Ohjelma 33
* 12.01.2017
* Toisenlainen koodilukko
**************************************/

// MÄÄRITTELYT:
// Yleiset muuttujat
// Digitaali tulot ja lähdöt
      boolean Bol_Tila = false; // DigiInputin muuttuja
      const int PainVas = 2; // Vasemman painikkeen pinni
      boolean Bol_PainVas = false;
      boolean Diu_PainVas = false;
      const int LEDPun = 5; // Punaisen LEDin pini
      const int PainOik = 3; // Oikean painikkeen pinni
      boolean Bol_PainOik = false;
      boolean Diu_PainOik = false;
      const int LEDVih = 4; // Vihreän LEDin pinni
// 7-sekvenssinäyttö
      const int Con_DS_Data = 6; // Sarjadatan syöttö
      const int Con_SHCP_Kello = 7; // Sarjadatan kellotus
      const int Con_STCP_Siirto = 8; // Syöttö rinnanlähtöön
      boolean Arr_Data[8] = {0,0,0,0,0,0,0,0};// Näyttö
      int Arr_Numerot[9]; // Arvottujen numeroiden taulukko
      int Int_Numero = 0;
// Avainkoodi: 7352 yhteensä 17, 1 .. 9 yhteensä 45
      int Arr_Oikea[4] = {7,3,5,2}; // Yhteensä 17
      int Arr_Vaara[5] = {1,4,6,8,9}; // Yhteensä 28
      int Int_OikeaSumma = 0;
      int Int_VaaraSumma = 0;
// Sekvenssimääritykset
      int Seq_Hallinta = 1;
      const int Con_Tauko = 25;
      int Int_Tauko = 0;
      const int Con_NayttoVii = 125;
      int Int_NayttoVii = 0;

// ALIOHJELMAT
// Viiveen aliohjelma
      int Fun_Viive(int Viive){
            Viive++;
            delay(1);
      return(Viive);
}// Viiveen loppu

// Tulot/lähdöt IO_Ali_v1
       boolean Fun_IO(int mode, int pinni, boolean tila){
            boolean bol_kosketin = false;
            switch (mode) {
      case 1:
            pinMode(pinni, INPUT);
            bol_kosketin = digitalRead(pinni);
            delay(10);
            return(bol_kosketin);
            mode = 0;
      break;
      case 2:
            pinMode(pinni, OUTPUT);
            digitalWrite(pinni, tila);
           mode = 0;
      break;
      case 3:
            pinMode(pinni, INPUT_PULLUP);
            bol_kosketin = digitalRead(pinni);
            delay(10);
            return(bol_kosketin);
            mode = 0;
      break;
      } //valintojen loppu
      } //funktion loppu

// Lasketaan oikeiden numeroiden summa
      int Fun_VertaaOikea(int num, int arpa){
       for(int i = 0; i < 4; i ++){
            if(arpa == Arr_Oikea[i]){
                  num = num + arpa;
            } // if loppu
      } // for loppu
      return num;
}// Vertailun loppu

// Lasketaan väärien numeroiden summa
      int Fun_VertaaVaara(int num, int arpa){
            for(int i = 0; i < 5; i ++){
                  if(arpa == Arr_Vaara[i]){
                        num = num + arpa;
                  } // if loppu
            } // for loppu
            return num;
      }// Vertailun loppu

      void Fun_Tyhjenna(){
            for(int i = 0; i < 8; i++){
                  digitalWrite(Con_DS_Data, 0);
                  digitalWrite(Con_SHCP_Kello, HIGH);
                  digitalWrite(Con_SHCP_Kello, LOW);
                  digitalWrite(Con_STCP_Siirto, HIGH);
                  digitalWrite(Con_STCP_Siirto, LOW);}
      }// Tyhjennyksen loppu

      void Fun_Sijoita(int data){
            int Seq_sijoita = 0;
// 7-sehmenttinäytön laidat
            boolean Arr_DataA[8] = {0,0,0,0,0,0,1,0};
            boolean Arr_DataB[8] = {0,0,0,0,0,1,0,0};
            boolean Arr_DataC[8] = {0,0,0,0,1,0,0,0};
            boolean Arr_DataD[8] = {0,0,0,1,0,0,0,0};
            boolean Arr_DataE[8] = {0,0,1,0,0,0,0,0};
            boolean Arr_DataF[8] = {1,0,0,0,0,0,0,0};
// 7-segmenttinäytön numerot
            boolean Arr_Data1[8] = {0,0,0,0,1,1,0,0}; // 1
            boolean Arr_Data2[8] = {0,1,1,1,0,1,1,0}; // 2
            boolean Arr_Data3[8] = {0,1,0,1,1,1,1,0}; // 3
            boolean Arr_Data4[8] = {1,1,0,0,1,1,0,0}; // 4
            boolean Arr_Data5[8] = {1,1,0,1,1,0,1,0}; // 5
            boolean Arr_Data6[8] = {1,1,1,1,1,0,1,0}; // 6
            boolean Arr_Data7[8] = {0,0,0,0,1,1,1,0}; // 7
            boolean Arr_Data8[8] = {1,1,1,1,1,1,1,0}; // 8
            boolean Arr_Data9[8] = {1,1,0,1,1,1,1,0}; // 9

      Seq_sijoita = data + 1;
            switch (Seq_sijoita) {
            case 1: for(int i = 0; i < 8; i++){
                 Arr_Data[i] = Arr_DataA[i];}break;
            case 2: for(int i = 0; i < 8; i++){
                  Arr_Data[i] = Arr_DataB[i];}break;
            case 3: for(int i = 0; i < 8; i++){
                  Arr_Data[i] = Arr_DataC[i];}break;
            case 4: for(int i = 0; i < 8; i++){
                  Arr_Data[i] = Arr_DataD[i];}break;
            case 5: for(int i = 0; i < 8; i++){
                  Arr_Data[i] = Arr_DataE[i];}break;
            case 6: for(int i = 0; i < 8; i++){
                  Arr_Data[i] = Arr_DataF[i];}break;
            case 7: for(int i = 0; i < 8; i++){
                  Arr_Data[i] = Arr_Data1[i];}break;
            case 8: for(int i = 0; i < 8; i++){
                  Arr_Data[i] = Arr_Data2[i];}break;
            case 9: for(int i = 0; i < 8; i++){
                  Arr_Data[i] = Arr_Data3[i];}break;
            case 10: for(int i = 0; i < 8; i++){
                  Arr_Data[i] = Arr_Data4[i];}break;
            case 11: for(int i = 0; i < 8; i++){
                  Arr_Data[i] = Arr_Data5[i];}break;
            case 12: for(int i = 0; i < 8; i++){
                  Arr_Data[i] = Arr_Data6[i];}break;
            case 13: for(int i = 0; i < 8; i++){
                  Arr_Data[i] = Arr_Data7[i];}break;
            case 14: for(int i = 0; i < 8; i++){
                  Arr_Data[i] = Arr_Data8[i];}break;
            case 15: for(int i = 0; i < 8; i++){
                  Arr_Data[i] = Arr_Data9[i];}break;
      }} // Sijoituksen loppu

      void Fun_Naytto(int j){
            Fun_Tyhjenna();
            Fun_Sijoita(j);
            for(int i = 0; i < 8; i++){
                  digitalWrite(Con_DS_Data, Arr_Data[i]);
                  digitalWrite(Con_SHCP_Kello, HIGH);
                  digitalWrite(Con_SHCP_Kello, LOW);
      }// Sarjakirjoitus loppu
      // Sarjadatan siirto lähtöihin
            digitalWrite(Con_STCP_Siirto, HIGH);
            digitalWrite(Con_STCP_Siirto, LOW);
      }// Näytön loppu

// Arvonnan aliohjema
      void Fun_Arvonta(){
            long Lng_Arpa = 0;
            int Int_Arpa = 0;
            int Int_No = 0;
            boolean Bol_Sama = false;
      do{
            Bol_Sama = false;
            Lng_Arpa = random(1, 10);
            Int_Arpa = Lng_Arpa;
            for(int k = 0; k < 9; k++){
                  if(Arr_Numerot[k] == Int_Arpa){
                        Bol_Sama = true;}}
                  if(Bol_Sama == false){
            Arr_Numerot[Int_No] = Int_Arpa;
            Int_No++;
            }
      }while(Int_No < 9);
      }//Arvonnan loppu


// ASETUKSET:
      void setup(){
            Serial.begin(9600);
            pinMode(Con_DS_Data, OUTPUT);
            pinMode(Con_SHCP_Kello, OUTPUT);
            pinMode(Con_STCP_Siirto, OUTPUT);
            randomSeed(analogRead(5));// Alustetaan sattuma anatulosta A5
            Fun_Tyhjenna();// Tyhjennetään 7-segmenttinäyttö
                  for(int i = 0; i < 9; i++){// Nollataan arvontataulukko
                        Arr_Numerot[i] = 0;
                  } // NOllaus loppu
      }// Asetuksen loppu

// PÄÄLOOPPI
void loop(){
// Painikkeiden luku
      Bol_PainVas = Fun_IO(1,PainVas,0);
      Bol_PainOik = Fun_IO(1,PainOik,0);

// Suorituksen hallintasekvenssi
      switch (Seq_Hallinta) {
      case 1: // Odotetaan käynnistystä
            Fun_Tyhjenna();
            if(Bol_PainVas == true && Bol_PainOik == true){
                  Seq_Hallinta = 2;
           }
      break;
      case 2: // Painikkeiden vapautus
          if (Bol_PainVas == false && Bol_PainOik == false){
                  for(int i = 0; i < 9; i++){
                       Arr_Numerot[i] = 0;}// Numerotaulukon tyhjennys
                        Seq_Hallinta = 3;
                  }
      break;
      case 3:// Näytön pyöräytys
            for(int k = 0; k < 3; k++){
                  for(int j = 0; j < 6; j++){
                        Fun_Naytto(j); delay(50);
            }}
            Fun_Tyhjenna();
            Seq_Hallinta = 4;
      break;
      case 4:// Numeroiden arvonta
            Int_Tauko = Fun_Viive(Int_Tauko);
            if (Int_Tauko > Con_Tauko){
                  Fun_Tyhjenna();
                  Fun_Arvonta();
                  Int_OikeaSumma = 0;
                  Seq_Hallinta = 5;
            }
      break;
      case 5:// Numeroiden testaus ja summaus
            for(int i = 0; i < 9; i++){
                  Int_Numero = Arr_Numerot[i];
                  Fun_Naytto(Arr_Numerot[i]+5);
            do{
                  Int_NayttoVii = Fun_Viive(Int_NayttoVii);
      //Oikean painikkeen käsittely
                  Bol_Tila = Fun_IO(1, PainOik, 0);
                  if(Bol_Tila == true && Diu_PainOik == false){
                        Diu_PainOik = true;
                        Int_OikeaSumma = Fun_VertaaOikea(Int_OikeaSumma, Int_Numero);
                  }// if painaminen
                  if(Bol_Tila == false){
                        Diu_PainOik = false;
                  }// if päästäminen
                  Bol_Tila = Fun_IO(1, PainVas, 0);
                  if(Bol_Tila == true && Diu_PainVas == false){
                        Diu_PainVas = true;
                        Int_VaaraSumma = Fun_VertaaVaara(Int_VaaraSumma, Int_Numero);
                  }// if päästäminen
                  if(Bol_Tila == false){
                        Diu_PainVas = false;
                  }// if päästäminen
            }while (Int_NayttoVii < Con_NayttoVii);
                  Int_NayttoVii = 0;
            }// for
                  Fun_Tyhjenna();
                  if(Int_OikeaSumma == 17 && Int_VaaraSumma == 28){
                        Seq_Hallinta = 6;
                  }else{
                        Seq_Hallinta = 7;
                  }
       break;
       case 6: // Oven avaus
            Fun_IO(2, LEDVih, HIGH);
            delay(2000);
            Fun_IO(2, LEDVih, LOW);
            Int_OikeaSumma = 0;
            Int_VaaraSumma = 0;
           Seq_Hallinta = 1;
      break;
      case 7: // Virheilmoitus
            for(int i = 0; i < 10; i++){
                  Fun_IO(2, LEDPun, HIGH);
                  delay(200);
                  Fun_IO(2, LEDPun, LOW);
                  delay(200);
            }
            Int_OikeaSumma = 0;
            Int_VaaraSumma = 0;
            Seq_Hallinta = 1;
      break;
      }// hallistasekvenssi loppu
      delay(1);
} // Pääohjelma LOPPU
 

Ei kommentteja:

Lähetä kommentti