tiistai 19. joulukuuta 2023

ASCII_65A

 Tällä ohjelmalla tulostetaan ASCII-merkkejä LCD-näytölle.

 

Taistelin epävarmana kehittäessäni yhtä sovellusta siitä, miten saisin ääkkösten isot kirjaimet (Ä ja Ö) tulostettua tuon pienelle kaksiriviselle LCD_näytölle. En netistä enkä muistakaan lähteistä löytänyt apua ongelmaani. Tuon puutteen ajamana syntyi tämä pieni ohjelma, jonka ajattelin ladata tänne sivullenikin. Toivottavasti joku hyötyisikin siitä.

 Ohjelmassa syötetään binäärimuodossa (byte) muuttuja väliltä B00000000 .. B11111111 ja tulostetaan LCD-näytön alariville. (Itse asiassa suurin luku menee hiukan tuon ylikin, sillä muuttujan kasvu ei pysähdy suurimpaan ASCII-numeroon, eli lukuun 255. Se on sentään mitätön haitta.)

 Painamalla painiketta muuttuja kasvaa aina kahdellakymmenellä ja jatkuu täten aina lukuun 260 saakka, minkä jälkeen se aloittaa uudelleen nollasta. Välillä riville ei tulostu mitään. Hyvin suuri osa merkeistä on kiinalaisia. Sieltä löytyvät kyllä kaikki standardin mukaiset ASCII-merkit ja myöskin pieni ä ja pieni ö, jotka pitää nyt sitten hyväksyä isojen kirjaimien tilalle.

 Sovelluksessa on hyödynnetty Rich UNO R3 kehityspohjaa, joten esimerkiksi painikevärähtelyä ei tarvitse itse ohjelmassa huomioida ja valvoa.

 

/***************************************
 *  Ohjelman nimi: LCD_AskiiB
 *  19.12.2023
 *  ASCII-bittien muunnos
 *  LCD-näytölle
 **************************************/
#include <Arduino.h>
// MÄÄRITTELYT:
// LCD-näyttö
#include <LiquidCrystal.h>
LiquidCrystal lcd(7, 6, 13,12,11,10);
byte alku = B00000000;

// Liityntöjen määrittelyt
  const int Con_TCH1 = 3;       // Kosketuspainike 1
  boolean Bol_TCH1 = false;
  int Seq_TCH1 = 0;
  boolean Kep_TCH1 = false;     // Ohjausbitti
  int kerta = 0;
  int No = 1;
 
// ALIOHJELMAT
// Merkkien päivitys LCD-näytölle
   void Fun_Jakso(int kohta, int rivi, int lukema){
    lcd.setCursor(kohta, rivi);
    lcd.print(lukema);
   }// Numerotulostuksen loppu
  void Fun_Merkki(int kohta, int rivi, char merkki){
    lcd.setCursor(kohta, rivi);
    lcd.print(merkki);
  }// Teksti_ali loppu
  void Fun_Lause(int kohta, int rivi, String teksti){
   lcd.setCursor(kohta, rivi);
   lcd.print(teksti);}

// ASETUKSET:
void setup(){                  
 Serial.begin(9600);    
  lcd.begin(20,2);
  lcd.clear();     
  pinMode(Con_TCH1, INPUT);      // Kosketuspainike
}// Asetuksen loppu

// PÄÄLOOPPI                   
void loop(){
// Painikkeen luku
   Bol_TCH1 = digitalRead(Con_TCH1);
   if(Bol_TCH1 == true && Kep_TCH1 == false && Seq_TCH1 == 0){
    Kep_TCH1 = true;
    Seq_TCH1 = 1;
    }// Painike 1
    
// Painikesekvenssi
switch (Seq_TCH1) {
    case 1:
    if(Kep_TCH1 == true && Kep_TCH1 == true){
      Fun_Lause(0,0,"ASCII jakso:");
      Fun_Jakso(12,0,kerta);
      Fun_Merkki(15,0,'-');
      Fun_Jakso(17,0,kerta+19);      
      for(int i=0;i<20;i++){
       Fun_Merkki(i,1,alku+i+kerta);}
      Kep_TCH1 = false;}
    if(Bol_TCH1 == false){
      Seq_TCH1 = 2;}
    break;
    case 2:
     No++;
     Seq_TCH1 = 3;
    break;
    case 3:
      kerta = kerta + 20;
      if(kerta > 260){
       kerta = 0;  lcd.clear();};
     if(Bol_TCH1 == false){Seq_TCH1 = 1;}
       Seq_TCH1 = 0;
    break;
}// Painikesekvenssin loppu
delay(1);
} // Pääohjelma LOPPU

tiistai 26. syyskuuta 2023

CW_64

Morsetuksen alkeita.

 Tämä ohjelma on oikeastaan vain runko sähkötyksen opetteluun. Tämä sisältää ainoastaan aakkosmerkit, jotka muodostuvat yhdestä, kahdesta tai kolmesta, joko pisteestä tai viivasta. Pisteistä lyhin on E-kirjain, eli yksi lyhyt piste. Viivoista lyhyin on T-kirjain. Pisin merkki on O-kirjain, joka muodostuu kolmesta viivasta. Muut ovat niiden väliltä ja muodostuvat pisteistä ja viivoista.

 

Tavoite luonnollisesti oli tehdä tämä ohjelma, sekä luoda siihen erilaisia toimintoja. Kun ohjelma käynnistyy, tulostuu näytölle pyyntö painaa kosketuspainiketta TCH1 ja valita sähkötyksen nopeus. Nopeus voidaan valita potentiometrilla, ja ainoastaan silloin, kun sormi viipyy painikkeella. Kun sormi irrotetaan, tulostuu näytölle ilmoitus, että sähkötys alkaa kahden sekunnin kuluttua.


 Kun sähkötysääni kuuluu, on näyttö tyhjä. Tänä aikana kuuluu ainoastaan summerista sähkötysmerkit. Viiden merkin välein on hiukan pidempi tauko. Merkkejä arvotaan 30 kappaletta (rajoite johtuu tuosta pienestä kaksirivisestä näytöstä). Kun sähkötys on loppu, tulostetaan näytölle kahteen riviin nuo arvotut sähkötysmerkit. Jos merkit on kirjoitettu paperille kuuntelun aikana, voidaan niiden oikeellisuus ja oppimisen onnistuminen tarkastaa näytöltä.

 Kun painiketta TCH1 painetaan uudestaan, voidaan nopeus valita jälleen (ei välttämätöntä), ja suorittaa uusi harjoitus. Tuo nopeusasetus (15 .. 93 merkkiä minuutissa) on lähinnä symbolinen, sillä kaikkia sähkötysmerkkejä tämä harjoitus ei sisällä. Tavoite on ainoastaan, että voidaan muodostaa sekä hidasta että nopeaa sähkötystä.

 /***************************************

 *  Ohjelman nimi: Opi_CWF_64
 *  26.09.2023
 *  Lyhyt toimintaselostus:
 *  Tulostaa pillille 1, 2, ja 3
 *  merkkisiä sähkötyskirjaimia.
 *  Sähkötysnopeutta voidaan säätää käsin.
 **************************************/

// MÄÄRITTELYT:
#include <LiquidCrystal.h>
  LiquidCrystal lcd(7, 6, 13,12,11,10);
  const byte api = B11100001; // ä
  const byte opi = B11101111; // ö

// Taulukoita ja muuttujia
    const int Con_Tapit = 30;         // Merkkitaulukon pituus
    char Merkit[] = "EISTANMUDORKWG"; // Arvottavat merkkialkiot
    String Tulos = "";      
    int Int_TaulPit = 13;             // Merkkien lukumäärä
    int Int_Sattuma = 0;
    const int ViKe = 5;               // Viivekerroin    
    boolean Kep_Tulosta = false;      // Tulostus LCD-näytölle    
    int Int_Rivi = 0;                 // LCD-näytön rivi
    int Int_Kohta = 0;                // LCD-näytön kohta     

// Liityntöjen määrittelyt
// Digitaalinen tulo
    const int Con_TCH1 = 3;       // Kosketuspainike 1
// Digitaalinen lähtö
    const int Con_Pilli = 9;      // tämä on BUZZeri
// Analoginen tulo    
    const int Con_Nopeus = 0;     // Potentiometrin tulo
    int Int_NopRaaka = 0;
    int Int_Nopeus = 0;          // sähkötyksen nopeussäätö
    const int Con_Arpa = 1;

// Sähkötykseen ja ohjaukseen liittyvät
    int Int_Jakso = 0;            // Tähän lasketaan nopeus
    boolean Bol_TCH1 = false;
    boolean Diu_TCH1 = false;     // Painiketieto yhden kierroksen ajan
    boolean Kep_teksti_1 = true;  // Alkutekstin kirjoitus

// Sekvenssit
    int Seq_TCH1 = 1;             // Kosketuspainikkeen käsittely
    int Seq_CW_Merkit = 0;        // Merkkien valinta taulukosta

//********************************************************************
// ALIOHJELMAT
// Näytön aliohjelmat
void Fun_Tyhjenna(int rivi){
  lcd.setCursor(0, rivi);
  lcd.print("                    ");
}// LCD rivityhjennys loppu

// Tekstien päivitys
void Fun_Teksti(int kohta, int rivi, char teksti){
  lcd.setCursor(kohta, rivi);
  lcd.print(teksti);
}// Teksti_ali loppu
void Fun_Lause(int kohta, int rivi, String teksti){
  lcd.setCursor(kohta, rivi);
  lcd.print(teksti);
}// Lause_ali loppu
//Numeroiden päivitys
void Fun_Nopeus(int kohta, int rivi, int nopeus){
  lcd.setCursor(kohta, rivi);
  lcd.print(nopeus);
}// Mittaus_ali loppu

// Äänimerkin aktivointi
    void Fun_Pilli(int kertaa, int kesto){
      int k = kertaa;
      int pituus = Int_Jakso * kesto;
      for(int i = 1; i <= k; i++){
      digitalWrite(Con_Pilli, true);
      delay(pituus);
      digitalWrite(Con_Pilli, false);
      delay(Int_Jakso);
     }// Pillin loppu
    }// foorin loppu


//********************************************************************
// ASETUKSET:
void setup(){                  
 Serial.begin(9600);
  lcd.begin(20,2);
  lcd.clear();
Int_TaulPit = sizeof(Merkit);
  randomSeed(analogRead(Con_Arpa));
  pinMode(Con_TCH1, INPUT);  // Kosketuspainike
  pinMode(Con_Nopeus, INPUT); // Potentiometrin tulo
  pinMode(Con_Pilli, OUTPUT);  
}// Asetuksen loppu


//********************************************************************
// PÄÄLOOPPI                   
void loop(){

// Tyhjennetään näyttö ja ladataan alkutekstit
if (Kep_teksti_1 == true){
    Fun_Lause(1, 0, "Paina TCH1 ja");
    Fun_Lause(1, 1, "valitse nopeus");
    Kep_teksti_1 = false;}

// Kosketuspainikkeen hallinta
Bol_TCH1 = digitalRead(Con_TCH1);
switch (Seq_TCH1) { // Kosketuspainikkeen sekvenssi
  case 1:
    if(Bol_TCH1 == true){
      Fun_Tyhjenna(0);Fun_Tyhjenna(1);
      Seq_TCH1 = 2;
      }
    break;
  case 2:
    Fun_Lause(1, 0, "Valitse nopeus:");
      Seq_TCH1 = 3;
  break;
  case 3: // Määritellään sähkötysnopeus
 Bol_TCH1 = digitalRead(Con_TCH1);
       // Määritellään sähkötyksen nopeus ja merkin kesto
          Int_NopRaaka = analogRead(Con_Nopeus);
             Int_Nopeus = Int_NopRaaka / 13 + 15; //13 + 20;
          Int_Jakso = 150 -  Int_Nopeus; // Sähkötyksen nopeus potikalta
            Fun_Nopeus(3,1,Int_Nopeus);
            Fun_Lause(6,1,"m/min");
     if(Bol_TCH1 == false){
        Fun_Tyhjenna(0);Fun_Tyhjenna(1);
        Seq_TCH1 = 4;}
   break;

  case 4: // Ilmoitus alkamisesta
    Fun_Lause(1,0,"S");
    Fun_Teksti(2,0,B11100001);
    Fun_Lause(3,0, "hk");
    Fun_Teksti(5,0,B11101111);    
    Fun_Lause(6,0, "tys alkaa");
    Fun_Lause(3,1, "2s kuluttua.");
      delay(2000);
      Fun_Tyhjenna(0);Fun_Tyhjenna(1);
    Seq_TCH1 = 5;
   break;

  case 5: // Aktivoidaan sähktyssekvenssi
      Diu_TCH1 = true;
      Seq_TCH1 = 6;
   break;   
  case 6: // Tulostetaan näytölle arvotut merkit
      Int_Rivi = 0;Int_Kohta = 0;
      for(int k = 0; k < 30; k++){
      Fun_Teksti(Int_Kohta, Int_Rivi, Tulos[k]);
        Int_Kohta++;
          if(Int_Kohta == 5 || Int_Kohta == 11){
           Int_Kohta++;}
          if(Int_Kohta == 17){Int_Rivi = 1; Int_Kohta = 0;}
      } // forin loppy
        Seq_TCH1 = 7;
  break;  
  case 7: // Paluu uuteen arvontaan
      Bol_TCH1 = digitalRead(Con_TCH1);
         if(Bol_TCH1 == true){Seq_TCH1 = 1;}
            Tulos = "";
   break;  
}// Sekvenssin loppu

if(Diu_TCH1 == true){// eka on lkm. ja toka kesto
   for(int i = 0; i < Con_Tapit;i++){
     Int_Sattuma = random(0,14);
     Seq_CW_Merkit  = Int_Sattuma;
     Tulos = Tulos + Merkit[Int_Sattuma];
   switch (Seq_CW_Merkit){
    case 0:Fun_Pilli(1, 1);delay(ViKe*Int_Jakso);break;  // E
    case 1:Fun_Pilli(2, 1);delay(ViKe*Int_Jakso);break;  // I                      
    case 2:Fun_Pilli(3, 1);delay(ViKe*Int_Jakso);break;  // S
    case 3:Fun_Pilli(1, 2);delay(ViKe*Int_Jakso);break;  // T
    case 4:Fun_Pilli(1, 1);Fun_Pilli(1, 2);delay(ViKe*Int_Jakso);break;  // A
    case 5:Fun_Pilli(1, 2);Fun_Pilli(1, 1);delay(ViKe*Int_Jakso);break;  // N
    case 6:Fun_Pilli(1, 2);Fun_Pilli(1, 2);delay(ViKe*Int_Jakso);break;  // M
    case 7:Fun_Pilli(2, 1);Fun_Pilli(1, 2);delay(ViKe*Int_Jakso);break;  // U    
    case 8:Fun_Pilli(1, 2);Fun_Pilli(2, 1);delay(ViKe*Int_Jakso);break;  // D
    case 9:Fun_Pilli(3, 2);delay(3*Int_Jakso);break;  // O     
    case 10:Fun_Pilli(1, 1);Fun_Pilli(1, 2);Fun_Pilli(1, 1);delay(ViKe*Int_Jakso);break;  // R
    case 11:Fun_Pilli(1, 2);Fun_Pilli(1, 1);Fun_Pilli(1, 2);delay(ViKe*Int_Jakso);break;  // K
    case 12:Fun_Pilli(1, 1);Fun_Pilli(1, 2);Fun_Pilli(1, 2);delay(ViKe*Int_Jakso);break;  // W
    case 13:Fun_Pilli(1, 2);Fun_Pilli(1, 2);Fun_Pilli(1, 1);delay(ViKe*Int_Jakso);break;  // G
  }// Sekvenssin loppu
  if(i > 0 && (i+1)%5 == 0){delay(10*Int_Jakso);}// Pidempi tauko viiden välein
  }// Merkkivalinnan loppu
}// Arvonnan loppu
Diu_TCH1 = false; // DifUpp nollataan joka kierroksella
Kep_Tulosta = false;
delay(1);
} // Pääohjelma LOPPU

sunnuntai 28. toukokuuta 2023

OH2A_63

Tuossa Rich UNO R3 on odottanut varsin pitkään saadakseen sisäänsä sähköä ja ohjelmalauseita. Se on hyvä alusta kokeiluihin ja ohjelman kehitykseen, koska siinä on valmiina useita liityntöjä, kuten kosketuspainikkeita, analogiapuolella potentiometri, summeri sekä neljän numeron näyttö. Tässä mainitsin vain muutamia.

Näiden toimintaan löytyy paljon kirjasto-ohjelmia, mutta halusin lähteä liikkeelle ja kokeilemaan hyvin alhaiselta perustasolta. Hyvin pian kokeilut sortuivatkin osaamattomuuteen ja tiedon puutteeseen. Pääsin kuitenkin alkuun ja sain jotain yksinkertaista aikaan.

Alla oleva ohjelma piipittää morseaakkosia. Signaali käynnistyy painamalla hipaisupainiketta ja summeri sähköttää tekstin kuuluvasti. Sähkötysnopeus on säädettävissä arviolta välillä 20 .. 100 merkkiä minuutissa. Se ei kuitenkaan ole tarkka, sillä lyhin merkki on yksi (1) ”piste” (E-kirjain) ja pisin merkki viisi (5) ”viivaa” (numero 0). Muut ovat siltä väliltä.

Tarkoitukseni oli tulostaa nopeus tuossa näytössä, mutta siihen tämän hetkinen tietoni ja osaamiseni ei riittänyt. Kaiketi jossain vaiheessa pitänee ladata kirjasto-ohjelmia, jotta pääsen aiheessa eteenpäin.

Tässä ohjelmassa ”sanoma” on CQ CQ DE OH2A ...K. Siinä on aluksi kaksi (2) yleistä kutsua (CQ), sen jälkeen tässä (DE) sekä kutsu OH2A ja lopuksi kuuntelen (K). Tuo kutsu on Suomen radioamatööriliiton kutsu, joten ohjelma kutsuu uusia jäseniä liittymään radioamatööriharrasteen pariin. Tervetuloa minunkin puolestani!

Liukukytkimillä voi aktivoida eri toimintoja sekä tulopuolelle että ohjauspuolelle. Tässä ohjelmassa on valittu hipaisukytkimellä (TCH1, D3) sähkötyksen käynnistys. Summerin ohjaus (BUZZER, D9) sekä potentiometrin analogiatulo (KNOB, A0).

Liukukytkimen luenta on ohjelmallisesti yksinkertainen, koska tässä ei tarvitse ottaa huomioon mitään kosketinvärähtelyitä tai muita häiriötekijöitä. Kun ohjelma huomaa, että kosketinta on painettu, se asettaa ykköseksi (1) bitin (Diu_TCH1), joka käynnistää sähkötyksen. Seuraavaksi ohjelma siirtyy odottamaan painikkeen vapautumista. Käynnistysbitti (Diu_TCH1) nollataan jokaisella ohjelmakierroksella ohjelman lopussa. Täten seuraava sähkötysjakso alkaa vasta uudesta painamisesta.

 


Alla oleva ohjelma on varsin yksinkertainen ja suoraviivainen. Painike on kaksiaskelinen sekvenssi. Summerin aliohjelmalle ladataan se, onko kyse pisteestä (1) vai viivasta (2). Viivan kesto on kaksi kertaa piste. Kokonainen merkki syntyy peräkkäisistä kutsuista. Merkkien väli on myös riippuvainen pisteen kestosta.

 Tässä ohjelmassa käytössä ovat seuraavat valinnat:

            10 liukukytkimet:   vasen 3 = painike 1, oikea 3 = summeri, oikea 6 = potikka

            9 hipaisupainike TCH1

            4 summeri

            12 potentiometri

 

 

/*****************************************************

* Ohjelman nimi: OH2A

* 28.05.2023

* Rich UNO kehitystä

* Piippaa amatöörikutsun painiketta painettaessa.

****************************************************/

#include <Arduino.h>

// MÄÄRITTELYT:

// Liityntöjen määrittelyt

// Digitaalinen tulo

const int Con_TCH1 = 3; // Kosketuspainike 1

const int Con_Ana_1 = 0; // Potentiometrin tulo


// Digitaalinen lähtö

const int Con_LEDiA = 5; // Merkkivalo

const int Con_Pilli = 9; // tämä on BUZZeri


// Muutujamäärittelyt

int Int_KierLasku = 0; // Jaksotetaan ohjelmakierroksilla

const int Con_Jakso = 140; // Perusjakso piste ja tauko

int Int_Jakso = 0; // Tähän lasketaan nopeus

boolean Bol_TCH1 = false;

boolean Kep_TCH1 = false; // Kosketuspainikkeen tilatieto

boolean Diu_TCH1 = false;

boolean Kep_Pilli = false; // Summerin tila

int Int_Nopeus = 0; // sähkötyksen nopeussäätö


// Sekvenssit

int Seq_TCH1 = 1; // Kosketuspainikkeen käsittely

int SeqPilli = 1; // Vingutuksen sekvenssi


// ALIOHJELMAT:

// Äänimerkin aktivointi

void Fun_Pilli(int kertaa, int kesto){

    int k = kertaa;

    int pituus = Int_Jakso * kesto;

        for(int i = 1; i <= k; i++){

            digitalWrite(Con_Pilli, true);

            delay(pituus);

            digitalWrite(Con_Pilli, false);

            delay(Int_Jakso);

        }//foorin loppu          

}// Pillin loppu


// ASETUKSET:

void setup(){

    Serial.begin(9600);

    pinMode(Con_TCH1, INPUT); // Kosketuspainike

    pinMode(Con_Ana_1, INPUT); // Potentiometrin tulo

    pinMode(Con_Pilli, OUTPUT);

}// Asetuksen loppu


// PÄÄLOOPPI

void loop(){ // Valvotaan painiketta ja muodostetaan ohjauskoodi

    Int_Nopeus = analogRead(Con_Ana_1);

    Int_Jakso = Con_Jakso - Int_Nopeus / 10; // Sähkötyksen nopeus potikalta


    Bol_TCH1 = digitalRead(Con_TCH1);

        switch (Seq_TCH1) { // Kosketuspainikkeen sekvenssi

            case 1:

                if(Bol_TCH1 == true){

                    Diu_TCH1 = true;

                    Seq_TCH1 = 2;

                }

        break;

        case 2:

                if(Bol_TCH1 == false){Seq_TCH1 = 1;}

        break;

    }// Sekvenssin loppu


// 1. montako merkkiä 2. kesto, 3. tauon pituus

if(Diu_TCH1 == true){

    for(int i = 1;i <= 2; i++){

        Fun_Pilli(1, 2);                  // C

        Fun_Pilli(1, 1);

        Fun_Pilli(1, 2);

        Fun_Pilli(1, 1);

        delay(3*Int_Jakso);

        Fun_Pilli(2, 2);                  // Q

        Fun_Pilli(1, 1);

        Fun_Pilli(1, 2);

    delay(3*Int_Jakso);

    } // kaksi yleistä kutsua

        Fun_Pilli(1, 2);                  // D

        Fun_Pilli(1, 1);

        Fun_Pilli(1, 1);

    delay(3*Int_Jakso);

        Fun_Pilli(1, 1);                 // E

    delay(3*Int_Jakso);             // tässä

        Fun_Pilli(3, 2);                 // O

    delay(3*Int_Jakso);

        Fun_Pilli(4, 1);                  // H

    delay(3*Int_Jakso);

        Fun_Pilli(2, 1);                 // 2

        Fun_Pilli(3, 2);

    delay(3*Int_Jakso);

        Fun_Pilli(1, 1);                  // A

        Fun_Pilli(1, 2);

    delay(7*Int_Jakso);               // OH2A

        Fun_Pilli(1, 2);                  // K

        Fun_Pilli(1, 1);

        Fun_Pilli(1, 2);

    delay(3*Int_Jakso);             // kuuntelen

}// kutsu loppu


Diu_TCH1 = false; // DifUpp nollataan joka kierroksella

delay(1);

} // Pääohjelma LOPPU

 

sunnuntai 27. helmikuuta 2022

HexNoppa_62

Lautapelit ovat nousseet jälleen suosioon. On siis näin digitaaliaikana syytä päivittää arpakuutio. Tässä tapauksessa ei tietenkään ole kuutio, vaan arvottavat luvut välillä 0 ..15, eli hexaluvut 0..F. 

Arpakuutiossa on mahdollista saada luvut välillä 1..6, mutta mikä estäisi vaeltamasta Afrikan tähdessä hiukan pidempiäkin matkoja samalla kertaa. Tai saamalla nollan (0), siirtää vuoro seuraavalle. Toki luvut 1..6 ovat tässäkin mahdollisia, ja silloin LEDejäkin tarvitaan ainoastaan kolme kappaletta. 

Itse asiassa tärkeämpänä tavoitteena oli harjoitella sitä, miten kokonaisluvun arvo voidaan muuntaa biteiksi ja osoittaa Arduinon lähdössä binäärinumerona. Näin digitaalivaltakautena on kunkin hyvä oppia myös tämä järjestelmä, millä meidän arkemme käytännössä toimii. Tietysti, ja varmaan varsinkin alussa, tämä keino herättää myös keskustelua siitä, minkä numeron arpa tällä kerralla tarjosi. Tuossa kuvassa on tietysti luku 11 eli oikealta vasemmalle laskien 1 + 2 + 8 = 11. 

 

/***************************************

* Ohjelman nimi: HexNopB

* 27.2.2022

* Lyhyt toimintaselostus:

* Arvotaan hexaluvut välillä 0 .. F, ja

* tulostetaan ne neljällä LEDillä

**************************************/


// MÄÄRITTELYT:

// Liityntöjen määrittelyt

const int Con_DO_0 = 11;

const int Con_DO_1 = 10;

const int Con_DO_2 = 9;

const int Con_DO_3 = 8;

const int Con_In = 2;

boolean Bol_In = 0;

// Muita määrittelyjä

const int Con_Vi = 100;

int Int_Vi = 0;

int Seq_Kytkin = 1;

int Int_Arpa = 0;

byte Bin_0 = 0;

byte Bin_1 = 0;

byte Bin_2 = 0;

byte Bin_3 = 0;

// ALIOHJELMAT

void Fun_Tulostus(){

Serial.print("Arpaluku: "); Serial.println(Int_Arpa);

}// Tulostuksen loppu

// ASETUKSET:

void setup(){

Serial.begin(9600);

pinMode(Con_DO_0, OUTPUT);

pinMode(Con_DO_1, OUTPUT);

pinMode(Con_DO_2, OUTPUT);

pinMode(Con_DO_3, OUTPUT);

pinMode(Con_In, INPUT_PULLUP);

}// Asetuksen loppu


// PÄÄLOOPPI

void loop(){

Bol_In = digitalRead(Con_In);

// Muodostetaan satunnaisluku

switch (Seq_Kytkin) {

case 1:

if (!Bol_In) {Int_Vi += 1;} // kosketinvärähtelyviive

if (Int_Vi > Con_Vi){

Int_Arpa = random(0, 16); // arvonta välillä 0 .. 15

// Fun_Tulostus();

// Luetaan bitit

Bin_0 = bitRead(Int_Arpa, 0); Bin_1 = bitRead(Int_Arpa, 1);

Bin_2 = bitRead(Int_Arpa, 2); Bin_3 = bitRead(Int_Arpa, 3);

// Kirjoitetaan bitit

digitalWrite(Con_DO_0, Bin_0); digitalWrite(Con_DO_1, Bin_1);

digitalWrite(Con_DO_2, Bin_2); digitalWrite(Con_DO_3, Bin_3);

Seq_Kytkin = 2;}

break;

case 2:

if (!Bol_In == 0){

Int_Vi = 0;

Seq_Kytkin = 1;

}

break;

} // Arvonnan loppu

delay(1);

} // Pääohjelma LOPPU

 

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