tiistai 13. huhtikuuta 2021

SHAKKIKELLO

 

Onkin ollut aikaa ajatella mitä sitä keksisi. Uutisista on tullut runsaasti ohjeita miten varoa, väistää, suojautua ja siivota. Kaikkea kertyy vuosien mittaan ja laatikoita siivotessa eteen sattui shakkilauta. En tosin enää pelaa, enkä ole koskaan ollutkaan kovin hyvä pelaaja, mutta ajattelin, että shakkikellon tekeminen Arduinolle olisi ehkä mielekäs haaste.

 Shakissa ja ohjelmoinnissa on se selkeä ero, että pelatessa jokainen virhe johtaa häviön suuntaan, mutta ohjelmoinnissa oppimiseen (kunhan onnistuu korjaamaan). Niin tämäkin pienin askelin ja virheiden kautta lähti ilokseni toimimaan suunnitellulla tavalla.

 Kotelo voisi olla hienompikin. On siinä kuitenkin se hyvä puoli, että se on kierrätyskamaa. Käyttöenergiansa kello saa 9 V:n neppariparistosta. LCD-näyttö saa syöttönsä Arduinon 5 V:n lähdöstä.

  Kello käynnistyy virtakytkimestä. Painamalla painiketta, lähtee vastapelaajan tikutus käyntiin. Kun vastapelaaja saa siirtonsa tehtyä ja painaa omaa nappiaan, pysähtyy hänen kellonsa ja toinen käynnistyy. Kellot laskevat sekunteja, minuutteja ja tunteja. Tunnit on rajoitettu yhdeksään (9). Maksimi peliaika on siis 2 * 9 tuntia, 59 minuuttia ja 59 sekuntia. Minuutit ja sekunnit kiertävät (59 => 0), niitä ei pysäytetä, mutta tuon jälkeen pitää kirjata käsin.


 OHJELMA 61

 /************************************************
 *  Ohjelman nimi: ShakkiKello61_v6
 *  11.04.2021
 *  Shakkikello LCD-näytöllä
 *  A-painike käynnistää B:n ajan laskun
 *  ja B-painike A:n ajanlaskun
 *  Molemmat painikkeet lyhyesti, pysäyttää ajan
 *  Molemmat yli 2s nollaa ajat.
 ***********************************************/

// MÄÄRITTELYT:
// LCD-näyttö
#include <LiquidCrystal.h>
LiquidCrystal lcd(3, 2, 7,6,5,4);

// Tekstit
    char tx1[] = "PELAAJA B";
    char tx2[] = "PELAAJA A";   

// Liityntöjen määrittelyt
    // Pelaajan A määrittelyt
    const int Con_PelA = 8; // Pelaajan A painike
    boolean Bol_PelA = false;
    boolean Bol_ValintaA = false;
    boolean Kep_PelA = false;
    int Seq_PelA = 1;
    int Int_SekA = 0;
    int Int_MinA = 0;
    int Int_TunA = 0;
    int Int_PainA = 0;
    unsigned long Ulo_PelA  = 0;

   // Pelaajan B määrittelyt
    const int Con_PelB = 9;// Pelaajan B painike
    boolean Bol_PelB = false;    
    boolean Bol_ValintaB = false;
    boolean Kep_PelB = false;    
    int Seq_PelB = 1;
    int Int_SekB = 0;
    int Int_MinB = 0;
    int Int_TunB = 0;
    int Int_PainB = 0;
    unsigned long Ulo_PelB  = 0;     

    // Pelaajien yhteiset määrittelyt
    boolean Bol_Tauko = false;
    unsigned long Ulo_Nollaus = 0;

// Kellon ja tahdistuksen määrittelyt
    unsigned long Ulo_MilliSek = 0;
    unsigned long Ulo_Tulostus = 0;
    int Int_Viive = 100;

// ALIOHJELMAT
// Tekstin päivitys
void Fun_Teksti(int kohta, int rivi, char teksti[]){
  lcd.setCursor(kohta, rivi);
  lcd.print(teksti);
}// Teksti_ali loppu

// Ajan päivitys
void Fun_Aika(int kohta, int rivi, int lukema){
  lcd.setCursor(kohta, rivi);
  lcd.print(lukema);
}// Aika_ali loppu

// ASETUKSET:
void setup(){                  
 Serial.begin(9600);    
  lcd.begin(20,2);
  lcd.clear();     
  Fun_Teksti(0, 0, tx1);
  Fun_Teksti(11, 0, tx2);
  pinMode(Con_PelA, INPUT_PULLUP);
  pinMode(Con_PelB, INPUT_PULLUP);  
}// Asetuksen loppu


// PÄÄLOOPPI                   
void loop(){
// Sisäisen kellon päivitys
Ulo_MilliSek = millis();

// Pelaajat A & B
Bol_PelA = !digitalRead(Con_PelA);
Bol_PelB = !digitalRead(Con_PelB);
Bol_ValintaA = Bol_PelA == 1 && Bol_PelB == 0;
Bol_ValintaB = Bol_PelA == 0 && Bol_PelB == 1;
Bol_Tauko = Bol_PelA == 1 && Bol_PelB == 1;
if(Bol_Tauko == true){ Kep_PelA = false; Kep_PelB = false;}
if(Bol_Tauko == false){Ulo_Nollaus = Ulo_MilliSek;}
if(Bol_Tauko == true && Ulo_MilliSek > (Ulo_Nollaus + 1000)){
    Int_SekA = 0; Int_MinA = 0;  Int_TunA = 0;
    Int_SekB = 0; Int_MinB = 0;  Int_TunB = 0;  
}

// Pelaajan B valinta painikkeella A
switch (Seq_PelA) {
  case 1:
    if(Bol_ValintaA == false) {Int_PainA = 0;
    }else{Int_PainA++;}
    if(Bol_ValintaA == true &&  Int_PainA > Int_Viive){
      Kep_PelB = false; Kep_PelA = true;
      Ulo_PelB = Ulo_MilliSek;
      Seq_PelA = 2;}
    break;
  case 2:
    if(Bol_ValintaA == false){Seq_PelA = 1;}
    break;
}// Sekvenssin A loppu

// Pelaajan A valinta painikkeella B
switch (Seq_PelB) {
  case 1:
     if(Bol_ValintaB == false){Int_PainB = 0;
    }else{Int_PainB++;}     
    if(Bol_ValintaB == true &&  Int_PainB > Int_Viive){
      Kep_PelA = false; Kep_PelB = true;
      Ulo_PelA = Ulo_MilliSek;      
      Seq_PelB = 2;}
    break;
  case 2:
    if(Bol_ValintaB == false){Seq_PelB = 1;}
    break;
}// Sekvenssin B loppu

//A-pelaajan laskuri
if(Ulo_MilliSek > Ulo_PelA + 999 && Kep_PelB == true){
   Int_SekA++;
   Ulo_PelA = Ulo_MilliSek;}
   if(Int_SekA > 59){Int_SekA = 0; Int_MinA++;}
   if(Int_MinA > 59){Int_MinA = 0; Int_TunA++;}
   if (Int_TunA > 9){Int_TunA = 9;}

// B-pelaajan laskuri
if(Ulo_MilliSek > Ulo_PelB + 999 && Kep_PelA == true){
   Int_SekB++;
   Ulo_PelB = Ulo_MilliSek;}
   if(Int_SekB > 59){Int_SekB = 0; Int_MinB++;}
   if(Int_MinB > 59){Int_MinB = 0; Int_TunB++;}
   if (Int_TunB > 9){Int_TunB = 9;}

// Pelaajien ajan tulostus
if(Ulo_MilliSek > Ulo_Tulostus + 499){

  if(Int_TunB < 1){Fun_Teksti(1,1,"0");
  }else{Fun_Aika(1,1,Int_TunB);}
  Fun_Teksti(2,1,"t");
  if(Int_MinB < 10){Fun_Teksti(3,1,"0"); Fun_Aika(4,1,Int_MinB);
  }else{Fun_Aika(3,1,Int_MinB);}
  Fun_Teksti(5,1,"m");
  if(Int_SekB < 10){Fun_Teksti(6,1,"0"); Fun_Aika(7,1,Int_SekB);
  }else{Fun_Aika(6,1,Int_SekB);}
  Fun_Teksti(8,1,"s");

 if(Int_TunA < 1){Fun_Teksti(12,1,"0");
  }else{Fun_Aika(12,1,Int_TunA);}
  Fun_Teksti(13,1,"t");
  if(Int_MinA < 10){Fun_Teksti(14,1,"0"); Fun_Aika(15,1,Int_MinA);
  }else{Fun_Aika(14,1,Int_MinA);}
  Fun_Teksti(16,1,"m");
  if(Int_SekA < 10){Fun_Teksti(17,1,"0"); Fun_Aika(18,1,Int_SekA);
  }else{Fun_Aika(17,1,Int_SekA);}
  Fun_Teksti(19,1,"s");
 
  Ulo_Tulostus = Ulo_MilliSek;
}// Aikatulostuksen loppu
 delay(1);
} // Pääohjelma LOPPU

 

perjantai 8. toukokuuta 2020

LATAUSVAHTI 59

Alun perin ajatus oli tehdä tämä juttu vain omaan tarpeeseen, eikä laittaa tänne blogiin. Toisaalta tässä sovelluksessa olen tehnyt jonkin kohdan jälleen hiukan toisella lailla, joten muutin mieleni. Ehkäpä tuosta ratkaisusta voisi olla oivallusta jonkun omaan projektiinsa.

Tämän ohjaimen tehtävänä on valvoa ja estää akkujen ylilataus. Minulla on mökillä kaksi akku- ja paneeliryhmää. Toinen on tuvan alla ja toinen aitan alla. Näillä molemmilla on omat kaupalliset säätäjänsä, mutta akut on yhdistetty kaapelilla. Jostain syystä, en tiedä mistä johtuu, nämä säätimet eivät oikein pelaa yhteen. Kun etelän puolen paneelit tuottavat voimakkaasti sähköä (ja idän puolen jo vähemmän), nousee aitan alla olevien akkujen jännite useita voltteja yli 14 voltin. Tämä tilanne ennen pitkää pilaa akut.

Tällä hetkellä siinä on käsikytkin, joten joudun valvomaan tilannetta päivän mittaan ja kytkemään idän puolen paneelit irti. Tämä toteutus tekee sen automaattisesti. Akkujen jännitteen noustessa yli 14,2 V, kytkeytyvät paneelit irti. Ne kytketään uudestaan säätimeen, kun akkujen jännite laskee alle 12,5 V.

Kuva1. Kolme eri tilannetta: käsi / automaatilla: paneeli kytkettynä / paneeli irti.
 
Tässäkin on siis käsikytkin, millä paneelit saa irti. Kun laite kytketään automaatille, eli käännetään kytkintä JA akkujen jännite on noiden rajojen välissä, ei laite toimi ennen kuin jompikumpi rajoista ohitetaan.
Tämän tilanteen olen toteuttanut tavallisesti kahden askeleen sekvenssillä. Siinä on helppo toteuttaa myös viive, millä eliminoidaan kosketinvärähtely.
Tässä toteutuksessa käsi- automaattikytkin ei ohjaa Arduinoa, vaan kytkee akkujännitteen releelle. Releen syöttöpisteestä maahan on vastuksen ja zenerin (4,7 V) sarjakytkentä, joiden väli on kytketty Arduinon tuloon (D5). Kun jännite on ylhäällä, on digitaalitulo ykkönen (1) ja ohjelma siirtyy automaatille.
Kuva 2. Aika tiukka hässäkkä.
Tämän muutoksen tunnistus, käsikäytöltä automaatille saa tapahtua vain kerran (0 → 1). Eli ohjaus saa tapahtua vain sillä ohjelmakierroksella, kun muutos havaitaan. Yleisesti käytetään termiä Difu = Differential Up. Tähän tarvitaan tilatieto käsiasennosta (Bol_EdelTila), mikä on epätosi (0) aina kun, kytkin on käsiasennossa. Kun kytkin käännetään automaattiasentoon, tunnistetaan ristiriita (Automaatti == tosi(1) JA vanha tieto == epätosi (0)), joten tällä ohjelmakierroksella päivitetään LEDit ja rele siihen tilaan, mitä akkujännite edellyttää. Heti tuon suorituksen jälkeen vanha tieto ladataan todeksi, joten tuo ehto ei toteudu seuraavilla ohjelmakierroksilla. Tässä oleellista on ohjelmarivien järjestys, eli suoritusjärjestys. Kun kytkin käännetään käsiasentoon, palautetaan vanha tieto epätositilaan.
Tässä tapauksessa ei viivettä tarvitse, koska kytkimen kosketin ei ole suoraan Arduinon tulossa. Jos silti ilmenee jotain epämääräisyyttä, niin voi zenerin rinnalle kytkeä pienen kondensaattorin.
 
Toteutin tämän projektin Arduino LEONARDO:lla pelkästään siitä syystä, että niitä oli jäänyt roikkumaan. Tosin sanoen halusin päästä tuosta tyypistä eroon. Se on huomattavasti herkempi vioittumaan kuin UNO. Minulla oli pari sellaista Leonardoa, jotka olivat ilman liittimiä. Ne olivat ainoita, jotka mahtuivat tuohon pieneen purkkiin, mutta molemmat olivat jo rikki. Jouduin irrottamaan yhdestä kaikki liittimet. Onneksi noita Leonardoja on enää yksi jäljellä. Ehkä sillekin jokin käyttö löytyy. Sen etuna on se, että sen omakäyttö vie vähemmän virtaa kuin UNO, mutta on myös paljon herkempi sähläyksille.

Varsinainen sotkuhan tuosta rakenteesta jälleen tuli, mutta onneksi se on pimennossa kotelon sisällä.

Kuva 3. Kytkennän oleelisimmat osat.
 
OHJELMA 59

/***************************************
* Ohjelman nimi: AkkuSuoja_v3
* 05.05.2020
* Mökille aitan akkujen suojaksi.
* Jos akkujen jännite nousee yli 14,2 V,
* kytketään rannan puoleiset paneelit irti.
* Palautus, kun jännite laskee alle 12,5 V.
* Laitteessa on myös käsikytkin.
* Analogia mttausalue 0 .. 17.0 V
**************************************/

// MÄÄRITTELYT:
// Liityntöjen määrittelyt
const int Con_LED_Kasi = 2;
const int Con_LED_Autom = 3;
const int Con_LED_Paalla = 4;
const int Con_LED_Pois = 6;
const int Con_Autom = 5;
boolean Bol_Automaatilla = false;
boolean Bol_EdelTila = false;
const int Con_Rele = 7;
const int Con_Ana_Tulo = 0;
int Int_Ana_Raaka = 0;

// Ohjauksen aliohjelma
void Fun_Ohjaus(int p1,int p2,int p3,int p4,int p5){
digitalWrite(Con_LED_Kasi, p1);
digitalWrite(Con_LED_Paalla, p2);
digitalWrite(Con_LED_Autom, p3);
digitalWrite(Con_LED_Pois, p4);
digitalWrite(Con_Rele, p5);
}// loppu

// ASETUKSET:
void setup(){
Serial.begin(9600);
pinMode(Con_LED_Kasi, OUTPUT);
pinMode(Con_LED_Paalla, OUTPUT);
pinMode(Con_LED_Autom, OUTPUT);
pinMode(Con_LED_Pois, OUTPUT);
pinMode(Con_Rele, OUTPUT);
pinMode(Con_Autom, INPUT);
}// Asetuksen loppu


// PÄÄLOOPPI
void loop(){
// Mittaukset ja siirtyminen Käsi -> Automaatti
Bol_Automaatilla = digitalRead(Con_Autom);
if(Bol_Automaatilla == false){ Bol_EdelTila = false;}
if(Bol_Automaatilla == true && Bol_EdelTila == false){
Fun_Ohjaus(0,1,1,0,1);
Bol_EdelTila = true;}
Int_Ana_Raaka = analogRead(Con_Ana_Tulo);

// Tilaohjaukset
if(Bol_Automaatilla == false){
Fun_Ohjaus(1,0,0,0,0);}
if(Bol_Automaatilla == true && Int_Ana_Raaka < 752 ){
Fun_Ohjaus(0,1,1,0,1);}
if(Bol_Automaatilla == true && Int_Ana_Raaka > 854 ){
Fun_Ohjaus(0,0,1,1,0);}
delay(1);
} // Pääohjelma LOPPU

perjantai 1. toukokuuta 2020

AKKUTESTERI 56

Kuva 1. Testeri kotelossaan päältä nähtynä
Tämän projektin tähtäimenä oli rakentaa testeri, millä voi tutkia akun jäljellä olevan kapasiteetin. Käyttö on sellaisia olosuhteita varten, missä aurinkopaneeleilla tai / ja tuuligeneraattoreilla ladataan akkuja. Toisin sanoen tilanteessa, missä verkkosähköä ei ole käytettävissä akkujen lataamiseen.
Näissä olosuhteissa on tyypillisesti useita akkuja rinnan. Ne voivat hyvinkin olla eri ikäisiä, ja tavoitteena on akku kerrallaan tutkia, onko kyseinen akku vielä käyttöön riittävä, vai syytä vaihtaa uuteen. Tämä ei ole mikään tarkkuusmittalaite, vaan pyrkimyksenä on saada tietää mitatun akun käyttökelpoisuus.

Kuva 2. Lataus- ja purkuvirtojen reittien kytkentä.
Kuva 3. Laite alta. Ylhäällä kuormituslamput.


Vaatimuksena ja edellytyksenä on siis mahdollisuus ladata testattava akku muista akuista. Suositeltavaa onkin aloittaa testaus silloin, kun akut ovat mahdollisimman täynnä. Tällöin lataukseen kuluu aikaa mahdollisimman vähän.
Kuormaksi on mahdollista valita yhdestä kolmeen 20 W lamppua. Kun kaikki kolme ovat käytössä, on kuorman virta noin 5 A, mikä on suurin piirtein sama, minkä esim. tuollainen Peltier-elementillä toimiva pieni jääkaappi. Testi toisin sanoen testi kertoo suoraan, miten kauan testattava akku pystyisi syöttämään tuollaista jääkaappia.


Annoin yllä ymmärtää, että laitteen tarkkuus ei ole kovin hyvä. Virhetekijöitä on mm. seuraavia:
  1. Akku ladataan noin 7 A virralla 14,5 V:n jännitteeseen. Akku ei välttämättä ole täysin latautunut jännitteen käväistessä tuossa tasossa. Tilannetta parantaa tosin se seikka, että lataus pulssituksen kautta tapahtuu siten että 2 s latausta ja 1 s taukoa. Tähän suurin syy on kuitenkin jäähdytyslevynä toimivan alumiinisen U-palkin pienuus. Se taas johtuu siitä, että kotelo vaihtui kehityksen aikana suuremmaksi.
  2. Kuva 4. Aikamoinen sotku.
    Hall-generaattorin (LCB-050B) mittausalue on 50 A. Joten esim. 5 A kuormalla tarkkuus ei voi olla juurikaan 10 % parempi.
  3. Akku puretaan 11 V tasoon. Tällöin akku ei ole täysin tyhjä, vaan kapasiteettia on vielä jäljellä. Tämä taso säästää akkua. Syvemmästä purkauksesta ei tässä tapauksessa ole mitään hyötyä, sillä (testimittauksissa ilmennyt) noin 70 – 80 % tarkkuus kertoo kyllä mainiosti akun käyttökelpoisuuden jatkossa.
  Laitetta on rakennettu ja kokeiltu pienissä erissä. Sisäkuvasta näkee, että se ei sovi kenellekään malliksi ja ohjeeksi; korkeintaan varoittavaksi esimerkiksi.
Kuva 5. Hakkuriohjaimen kytkentä.
Kuva 6. Erillisten releiden kytkentä. Koskettimet näkyvät kuvassa 2.
Alussa ohjelma tuntui varsin yksinkertaiselta. Ladataan akku, puretaan se ja samalla mitataan kuorman virta sekunnin välein, jaetaan lopuksi luvulla 3600, ja saadaan näin akun kapasiteetti ampeeritunneissa. Lopuksi ladataan akku.
Periaatteessa kylläkin näin. Tuota ajatusta vastaan sotii tieto, että esim. alla oleva ohjelma on versio 14. Eri ohjelmia syntyi kaikkiaan 36 kappaletta. Jotain pieniä yksityiskohtia piti kehittää erikseen. Suurin syy tähän oli häiriöt. Tässä käsitellään kuitenkin melko suuria tehoja, mitkä aiheuttavat jännitepiikkejä. Yksi ongelma on myös minussa. En suunnittele etukäteen sopivia komponentteja, vaan pengon laatikoita ja valitsen siltä, mitä löytyy ja mitkä saattaisivat olla soveltavissa. Tässä toimii jälleen vana viisaus: ”Älä tee niin kuin minä teen, vaan tee niin kuin minä sanon!”
Alussa käytössä oli ”sopiva” ja laadukas induktanssi. Nyt hakkurin (mikä siirtää tehoa akusta toiseen) teho nousi yli 200 watin, joten käytössä ollut neljän releen yksikkö (4 Relay Module) otti nokkiinsa, totesi että nyt riitti, katkoi painitun piirin kalvoja ja ilmoitti mielipiteensä savumerkein.
Piti rakentaa uusi yksikkö tukevammin relein. Käytin siinä autojen varaosareleitä (12 V 20/30 A). Samalla pienensin hakkurin oskillaattorin pulssisuhdetta ja latausvirtaa.
Yksi iso ongelma pysyi kuitenkin melko tiukasti mukana. Se oli häiriö, mikä sotki LCD-näytön. Näytöllä pyöri ihan mitä sattuu juuri kun testi oli edennyt ja Ah-lukema pian luettavissa. Tähän oleellisin ratkaisu oli ottaa LCD-näytön 5 V:n syöttö Arduinon vastaavasta lähdöstä. Silti muutama suodatuskonkka releiden kytkimissä oli paikallaan.
Laitteessa on myös reset-painike, sillä kytkettäessä laite akkuun (A1, syöttävä), on hetkellinen virtapulssi melko suuri. Kun akut on kytketty, onkin hyvä painaa resettiä, jonka jälkeen ohjelma latautuu uudestaan, ja kuorma voidaan valita ja testi käynnistää.



LAITTEEN KÄYTTÖ:
 
Muutama sana ohjelmasta ja käytöstä: Ensin kytketään laitteen liittimet syöttävään akkuun (A1). Kun näyttöön ilmestyy kehotus valita purkausteho ja käynnistys, voidaan liittimet kytkeä testattavaan akkuun (A2). Käynnistyksen jälkeen ohjelma tutkiin, ettei akkujen jännite-ero ole liian suuri. Seuraavaksi tutkitaan, onko akussa A1 riittävä jännite (yli 13,5 V). Jos kaikki on tähän saakka kunnossa, alkaa latausvaihe. Aluksi lataus tapahtuu induktanssin ja diodin kautta akkuun A2. Hetken perästä kytkeytyy hakkuri kahdeksi sekunniksi ja ”pumppaa” virtaa akkuun A2. Samalla valvotaan, ettei akun A1 jännite putoa alle 12 V:n. Jos näin tapahtuu, palataan edelliseen askeleeseen ja odotetaan, että syöttävän jännite on taas yli 13,5 volttia. Näin jatketaan kunnes testattava akku on ”täynnä”. Sen jälkeen irrotetaan akku A1 testattavasta akusta, puretaan akku ja mitataan kapasiteetti. Lopuksi ladataan akku ilman virtapumppausta. Jos akkujen jännite-ero (A1 – A2 < 0,5 V) on riittävä pieni, kytketään akut suoraan yhteen. Nämä eri vaiheet tulostetaan LCD-näytölle.


 
Ohjelma 56

/* Nimi: A_Kapa56_v14
Luotu: 30.04.2020
Luoja: Antti Isännäinen

KUVAUS:
Pyrkimyksenä on tutkia akun jäljellä oleva kapasiteetti
ympäristössä, missä enenergiaa talletetaan useisiin
rinnakkain kytkettyihin akkuihin. Ja missä energia
tuotetaan arinkopaneeleilla ja tuuligeneraattoreilla.
*/

// MÄÄRITTELYT:
#include <LiquidCrystal.h>
LiquidCrystal lcd(3, 2, 7,6,5,4);
const byte api = B11100001;

// Kellon määrittely
unsigned long Ulo_MilliSek = 0;
unsigned long Ulo_UusiMilliSek = 0;
unsigned long Ulo_PulsMiSek = 0;
int Int_Viive = 100;
int Int_LCDvii = 300;
int Int_Sekunti = 0;
int Int_ViSek = 0;
boolean Bol_LCD_tulostus = false;
unsigned long Ulo_UusiLCD = 0;
boolean Bol_AskelViive = false;
// Tekstit
char tx1[] = "V";
char tx2[] = "A";
char tx3[] = "A1";
char tx4[] = "A2";
char tx5[] = "VAL. KUORMA => START";
char tx6[] = "Kuorma: 3 == 5.25 A";
char tx7[] = "Kuorma: 2 == 3.50 A";
char tx8[] = "Kuorma: 1 == 1.75 A";
char tx9[] = "VALMISTELUT:";
char tx10[] = "ODOTTAA: A1>13.5V";
char tx11[] = "A2 LATAUS";
char tx12[] = "A2 PURKAUS";
char tx13[] = "VALMIS";
char tx14[] = "AKUT KYTKETTY YHTEEN";
char tx15[] = "Ah";

// Analogiatulot
const int Con_A1V = 0;
const int Con_A2V = 1;
const int Con_Virta = 2;

// Muuttujat
int Int_A1raaka = 0;
float A1_Jannite = 0.00;
int Int_A2raaka = 0;
float A2_Jannite = 0.00;
int Int_VirtaPohja = 0;
int Int_Virta = 0;
float Flo_Virta = 0.00;
boolean Bol_Summaus = false;
int Int_Kapasi = 0;
float Flo_Kapasi = 0.00;
float Flo_U_Ero = 0.00;

// Kuormituksen ja käynnistyksen valinta
const int Con_Start = 18;
boolean Bol_Start = false;
const int Con_Kuorma = 19;
boolean Bol_Kuorma = false;
int Seq_Kuorma = 1;
const int Con_K1 = 8;
const int Con_K2 = 10;
const int Con_K3 = 9;
const int Con_K4 = 11;
const int Con_Pulssi = 13;
boolean Bol_Pulssi = false;
int Int_Lamput = 3;
int Seq_Mittaus = 0;

// ALIOHJELMAT
// Näytön aliohjelmat
// Tekstien päivitys
void Fun_Teksti(int kohta, int rivi, char teksti[]){
   lcd.setCursor(kohta, rivi);
   lcd.print(teksti);
}// Teksti_ali loppu

//Numeroiden päivitys
void Fun_Mittaus(int kohta, int rivi, float lukema){
   lcd.setCursor(kohta, rivi);
   lcd.print(lukema);
}// Mittaus_ali loppu

// Releyksikön aliohjelma
void Fun_Rele(int k1,int k2,int k3,int k4){
   delay(50);
     digitalWrite(Con_K1, k1);
     digitalWrite(Con_K2, k2);
     digitalWrite(Con_K3, k3);
     digitalWrite(Con_K4, k4);
   delay(100);
}// Rele aliohjelman loppu

// Kuormavalinnan aliohjelma
void Fun_Kuormitus(int valinta){
   switch (valinta) {
   case 1:
     Fun_Teksti(0, 1, tx8);
   break;
   case 2:
     Fun_Teksti(0, 1, tx7);
   break;
   case 3:
     Fun_Teksti(0, 1, tx6);
   break;
   }// Sekvenssi loppu
}// Kuormitusvalinnan aliohjelman loppu

// ASETUKSET:
void setup(){
   Serial.begin(9600);
   lcd.begin(20,2);
   Fun_Teksti(0, 0, tx5);
   Fun_Teksti(0, 1, tx6);
   pinMode(Con_Kuorma, INPUT_PULLUP);
   pinMode(Con_Start, INPUT_PULLUP);
   pinMode(Con_K1, OUTPUT);
   pinMode(Con_K2, OUTPUT);
   pinMode(Con_K3, OUTPUT);
   pinMode(Con_K4, OUTPUT);
   pinMode(Con_Pulssi, OUTPUT);
   Fun_Rele(1, 0, 0, 0); // Asetetaan releiden perusasetus
   delay(Int_Viive * 7);
   Int_VirtaPohja = analogRead(Con_Virta);
   Ulo_UusiMilliSek = millis();
}// Asetuksen loppu

// PÄÄLOOPPI
void loop(){
  Ulo_MilliSek = millis();
  if(Ulo_MilliSek > Ulo_UusiMilliSek + (Int_Viive * 10)){
     Bol_Summaus = true;
     Int_Sekunti += 1;
     Int_ViSek += 1;
     Ulo_UusiMilliSek = millis();
} // Tahdistus loppu

// LCD-näytön tahdistus
if(Seq_Mittaus > 0){
   if(Ulo_MilliSek > Ulo_UusiLCD + Int_LCDvii){
     Bol_LCD_tulostus = true;
     lcd.clear();
     Ulo_UusiLCD = millis();
}} // Näytön päivityksen loppu

//Mittaukset
Int_A1raaka = analogRead(Con_A1V);
A1_Jannite = Int_A1raaka / 60.176;
Int_A2raaka = analogRead(Con_A2V);
A2_Jannite = Int_A2raaka / 60.176;
Int_Virta = analogRead(Con_Virta);
Int_Virta = Int_Virta - Int_VirtaPohja;
Flo_Virta = Int_Virta * 0.11;

// Kytkimet
Bol_Kuorma = !digitalRead(Con_Kuorma);
Bol_Start = !digitalRead(Con_Start);
if(Bol_Start == true && Seq_Kuorma > 0){
   Seq_Mittaus = 1;
   Seq_Kuorma = 0;
   Int_ViSek = 0;
} // Kuormavalinta loppu

// Kuormavalinnan sekvenssi
switch (Seq_Kuorma) {
   case 1:
     if(Bol_Kuorma == true && Ulo_MilliSek - Ulo_UusiMilliSek > (Int_Viive * 2)){
       Ulo_UusiMilliSek = Ulo_MilliSek;
       Int_Lamput --;if(Int_Lamput == 0){Int_Lamput = 3;}
       Fun_Kuormitus(Int_Lamput);
       Seq_Kuorma = 2;
   }
   break;
   case 2:
     if(Bol_Kuorma == false){Seq_Kuorma = 1;}
   break;
  }// Sekvenssin loppu

// PÄÄSEKVENSSI
// Akun kapasiteetin mittaussekvenssi
switch (Seq_Mittaus) {
   case 1: // Valmistelut
     if(Bol_LCD_tulostus == true){
       Fun_Teksti(0, 0, tx9);
       Fun_Teksti(0, 1, tx3);
       Fun_Teksti(11, 1, tx4);
       Fun_Mittaus(3, 1, A1_Jannite);
       Fun_Teksti(8, 1, tx1);
       Fun_Mittaus(14, 1, A2_Jannite);
       Fun_Teksti(19, 1, tx1);
} // LCD-tulostus
// Tarkastetaan, ettei akkujen jännite-ero ole liian suuri
   if(A2_Jannite > (A1_Jannite - 2.0)){
     if(Int_ViSek > 3){
       Seq_Mittaus = 2;
       Int_ViSek = 0;
   }// Viiven loppu
}// Jännitetarkastuksen loppu
break;
case 2: // Odottaa, että A1 > 13.5 V
   if(Bol_LCD_tulostus == true){
       Fun_Mittaus(3, 1, A1_Jannite);
       Fun_Teksti(8, 1, tx1);
       Fun_Teksti(0, 0, tx10);
       Fun_Teksti(0, 1, tx3);
       Fun_Teksti(11, 1, tx4);
       Fun_Teksti(19, 1, tx1);
       Fun_Mittaus(3, 1, A1_Jannite);
       Fun_Mittaus(14, 1, A2_Jannite);
   } // LCD-tulostus loppu
   if(A1_Jannite > 13.5 && Int_ViSek > 3){
       Int_ViSek = 0;
       Ulo_PulsMiSek = Ulo_MilliSek;
       Bol_Pulssi = true;
       Seq_Mittaus = 3;
   } // Jännite ja viivetarkastus loppu
break;
case 3: // Latausaskel
   if(Bol_LCD_tulostus == true){
       Fun_Teksti(0, 0, tx11);
       Fun_Teksti(0, 1, tx3);
       Fun_Teksti(8, 1, tx1);
       Fun_Teksti(11, 1, tx4);
       Fun_Teksti(19, 1, tx1);
       Fun_Mittaus(3, 1, A1_Jannite);
       Fun_Mittaus(14, 1, A2_Jannite);
       Fun_Mittaus(11, 0, -1 * Flo_Virta);
       Fun_Teksti(15, 0, tx2);
   } // LCD-tulostus loppu
   Fun_Rele(0, 0, 0, 0);
   if(Bol_Pulssi == true && Ulo_MilliSek > Ulo_PulsMiSek + 2000){
       Ulo_PulsMiSek = Ulo_MilliSek;
       Bol_Pulssi = false;
   } // Pulssitus päällä
   if(Bol_Pulssi == false && Ulo_MilliSek > Ulo_PulsMiSek + 1000){
       Ulo_PulsMiSek = Ulo_MilliSek;
       Bol_Pulssi = true;
   } // Pulssitus pois päältä
   digitalWrite(Con_Pulssi, Bol_Pulssi);

// Tarkastetaan, onko ladattava akku täysi
   if(A2_Jannite > 14.5){
       Bol_Pulssi = false; digitalWrite(Con_Pulssi, Bol_Pulssi);
       Int_ViSek = 0;
       Seq_Mittaus = 4;
   } // Latausgeneraattorin pysäytys loppu
// Tarkastetaan, laskeeko syöttöjännite liikaa
   if(A1_Jannite < 12.0){
       Bol_Pulssi = false; digitalWrite(Con_Pulssi, Bol_Pulssi);
       Seq_Mittaus = 2;}
   break;
   case 4: // Akun purkaus
   if(Bol_Summaus == true){
       Flo_Kapasi = Flo_Kapasi + 1 * Flo_Virta;
}// Kapasiteetin keruun loppu
   if(Bol_LCD_tulostus == true){
       Fun_Teksti(0, 0, tx12);
       Fun_Teksti(0, 0, tx12);
       Fun_Teksti(15, 1, tx2);
       Fun_Mittaus(12, 0, Flo_Kapasi / 3600);
       Fun_Teksti(17, 0, tx15);
       Fun_Teksti(0, 1, tx4);
       Fun_Teksti(8, 1, tx1);
       Fun_Mittaus(3, 1, A2_Jannite);
       Fun_Mittaus(11, 1, 1 * Flo_Virta);
   } // LCD-tulostus loppu
// Valitaan asettettu kuorma
   if(Int_Lamput == 3){Fun_Rele(1,1,1,0);}
       else if(Int_Lamput == 2){Fun_Rele(1,0,1,0);}
       else {Fun_Rele(1,1,0,0);}
   if(Int_ViSek > 5 && A2_Jannite < 11.0){
       Seq_Mittaus = 5;
       Int_ViSek = 0;
   }// viiven loppu
   break;
   case 5: // Lataus
     if(Bol_LCD_tulostus == true){
       Fun_Teksti(0, 0, tx11);
       Fun_Mittaus(11, 0, Flo_Kapasi / 3600);
       Fun_Teksti(17, 0, tx15);
       Fun_Teksti(0, 1, tx3);
       Fun_Mittaus(3, 1, A1_Jannite);
       Fun_Teksti(8, 1, tx1);
       Fun_Teksti(11, 1, tx4);
       Fun_Mittaus(14, 1, A2_Jannite);
       Fun_Teksti(19, 1, tx1);
   } // LCD-tulostus loppu
   Fun_Rele(0, 0, 0, 0);
   Flo_U_Ero = A1_Jannite - A2_Jannite;
   if(Int_ViSek > 5 && Flo_U_Ero < 0.5){
       Seq_Mittaus = 6;
       Int_ViSek = 0;
   }// viiven loppu
                        break; 
 case 6: // Valmis
   if(Bol_LCD_tulostus == true){
       Fun_Teksti(0, 0, tx13);
       Fun_Teksti(8, 0, tx4);
       Fun_Mittaus(11, 0, Flo_Kapasi / 3600);
       Fun_Teksti(17, 0, tx15);
       Fun_Teksti(0, 1, tx14);
   } // LCD-tulostus loppu
   Fun_Rele(1, 0, 0, 1);
break;
}// Mittaussekvenssi loppu

Bol_Summaus = false;
Bol_LCD_tulostus = false;
Bol_AskelViive = false;
delay(1);
} // Pääohjelma LOPPU

sunnuntai 29. syyskuuta 2019

MUUTAMIA TESTEJÄ

Kun näiden mikrokontrollerilevyjen parissa puuhailee ja kokeilee, niin silloin tällöin sattuu vahinkoja (puhun omasta kokemuksestani). Tämän jakson ideana on muutama pieni ja yksinkertainen ohjelma, joilla voi testata, onko Arduino kunnossa, ja miltä osin. Jos piirilevy ei vastaa työkaluohjelman lataustoimintaan, tulee ko. piiristä harmi. Toki siitä on hyvä käyttää joitakin toimivia osia, kuten liittimiä, LEDejä, 5 V-regulaattoria ja mahdollisesti joitain muitakin komponentteja.

 On toki mahdollista, että on onnistunut tuhoamaan ainoastaan jonkun I/O:n. Tämänkertainen jakso tähtää nimenomaan liityntöjen testaukseen. Enin osa testeistä on tehty Arduino UNO:lla. Joitakin myös Arduino LEONARDOLLA, mutta muut kuvassa näkyvät piirit eivät osallistuneet näiden ohjelmien kehitykseen.

 
HUOM! Noissa kaikissa kolmessa ohjelman määrittelyosiossa on mukana myös sarjaliikenne ohjelmointityökalulle (Serial.begin(9600);). Se ei ole tarpeen näiden testiohjelmien kannalta, mutta hyvähän sen on siellä olla, jos tekee muutoksia ohjelmaan ja haluaa ajankohtaista dataa näytölle.


DIGITAALILÄHDÖT
Ensimmäisenä on lähtöpinnien testaus. Niitähän on 12 kpl (D/I 2 .. 13). Mutta sen lisäksi analogiatulot (A0 .. A5) voidaan määritellä (14 .. 19) ja käyttää myös digitaalisena (paitsi LEONARDO, jossa täten toimivat vain A0 ja A1). Kokemukseni mukaan aiemmin myös D/I-pinnit D0 ja D1 olivat määriteltävissä tuloiksi ja lähdöiksi, mutta näissä testeissäni se ei onnistunut. Ne on nähtävästi täysin ”pyhitetty” sarjaliikenteelle. DIO0 on RX (sarjadatan tulo) ja DIO1 on TX (sarjadatan lähtö).

Indikaationa (ei kaivanne kytkentäkaaviota) voi olla esim. 1 kohm (sopiva valinta, sillä se etuvastuksena ei kuormita lähtöpinniä liikaa) vastus sarjassa LEDin kanssa. Eli: lähtöpinni, vastus, LED ja maa. (LEDin pitää olla oikein päin kytkettynä). Kun vastus vaihdetaan eri pinneihin, vilkahtaa myös levyllä oleva (OUT13 LED) säännöllisin välein
. Aika koostuu LOOPista 50 + 50 ms, sekä siitä, että luuppi käy läpi kaikki 17 lähtöä. Kun ulkoinen LED välähtää, toimii kyseinen lähtö oikein.

/***************************************
* OUT_Test_1
* 26.09.2019
* I/O-pinnien OUTput - testaus
**************************************/

// ASETUKSET:
void setup(){
Serial.begin(9600);
// Asetetaan DIGI ja ANA I/O-pinnit lähdöiksi
for(int i = 2;i <= 19; i ++){
   pinMode(i, OUTPUT);}
}// Asetuksen loppu

// PÄÄLOOPPI
void loop(){
for(int j = 2; j <= 19; j++){
   digitalWrite(j, HIGH);
   delay(50);
   digitalWrite(j, LOW);
   delay(50);
}
delay(1);
} // Pääohjelma LOPPU


DIGITAALITULOT
Tässä toisessa ohjelmassa käydään läpi kaikki digitaalitulot paitsi DIO-13, minkä LEDiä käytetään indikaattorina. Sen vuoksi se määritellään erikseen OUT-bitiksi. Muut määritellään kahdella eri luupilla tuloiksi, joissa on sisäinen ylös veto vastus (INPUT_PULLUP). Myös pääohjelma jakaantuu tämän vuoksi kahdeksi FOR-luupiksi. Kun jonkin tuloista kytkee maahan, suorittaa ohjelma aliohjelmakutsun. Tällöin piirilevyllä oleva LED (DIO-13) vilkkuu. Kyseinen tulopinni toimii siis oikein.

/***************************************
* IN_Test_1
* 26.09.2019
* I/O-pinnien INput - testaus
**************************************/
// Määrittlyt
int LEDout = 13;
boolean tila = false;

// LEDin aliohjelma
void Fun_LED(){
   digitalWrite(LEDout, HIGH);
   delay(150);
   digitalWrite(LEDout, LOW);
   delay(150);
}

// ASETUKSET:
void setup(){
Serial.begin(9600);
// Asetetaan pinnin 13 LED lähdöksi
pinMode(LEDout, OUTPUT);
// Asetetaan varsinaiset I/O-pinnit tuloiksi
for(int i = 2;i <= 12; i ++){
   pinMode(i, INPUT_PULLUP);}
// Asetetaan ANAlogia pinnit tuloiksi
for(int i = 14;i <= 19; i ++){
   pinMode(i, INPUT_PULLUP);}
}// Asetuksen loppu

// PÄÄLOOPPI
void loop(){
for(int j = 2; j <= 12; j++){
   tila = !digitalRead(j);
   if(tila == true){Fun_LED();}
} // for loppu
for(int j = 14; j <= 19; j++){
   tila = !digitalRead(j);
   if(tila == true){Fun_LED();}
} // for loppu
delay(1);
} // Pääohjelma LOPPU


ANALOGIATULOT
Tässä kolmannessa testiohjelmassa tutkitaan analogiatulojen toiminta. UNOssa ja LEONARDOssa niitä on kuusi (A0 .. A5) kappaletta. Niiden tulojännitealue on 0 .. 5 V. Tämä voidaan syöttää ulkopuolelta tai tehdä niin, että potentiometrin äärinavat kytketään Arduinon maan (GND) ja 5 V:n lähdön väliin ja potentiometrin liu'ulta viedään jännite mitattavaan analogiatuloon. Indikaattoriksi tähän testiin voidaan käyttää ensimmäisen testin LED / vastusyhdistelmää, kytkettynä johonkin analogialähtöön (PWM, pulssinleveysmodulaatio). Tässä ohjelmassa on käytetty lähtöä DO3. Lähdön jännite vaihtelee noin 1 kHz taajuudella nollan (0 V) ja viiden (5 V) voltin välillä, mutta näiden jännitteiden aikasuhde muuttuu ohjauksen mukaan. Analogiatulon muunnos on 10 bittinen, eli lukualue on 0 .. 1023 ja lähdön lukualue kahdeksan (8) bittinen, eli 0 .. 255, joten tuloarvo pitää jakaa neljällä. Kun potentiometria, säätää, vaihtuu tulojännite, minkä muutos ilmenee LEDin kirkkauden vaihteluna. Vaihtamalla potikan johtoa eri tuloihin voi tarkastaa jokaisen tulon toimivuus.


/***************************************
* ANA_Test_3
* 27.09.2019
* Muutetaan analogia-arvojen tuloarvo
* LEDin kirkkaudeksi
**************************************/

// MÄÄRITTELYT:
int Ana_Raaka = 0;
int Ana_Out = 0;
int PWM_LED = 3;

// ASETUKSET:
void setup(){
Serial.begin(9600);
}// Asetuksen loppu

// PÄÄLOOPPI
void loop(){
for(int i = 0; i <= 5; i++){
   Ana_Raaka = analogRead(i);
   Ana_Out = Ana_Raaka / 4;
   Ana_Out = constrain(Ana_Out, 0, 255);
}
analogWrite(PWM_LED, Ana_Out);
delay(1);
} // Pääohjelma LOPPU