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