keskiviikko 4. tammikuuta 2017

”Kostutuskeskus”.

Tämä osio on SARJA-37:n viimeinen sovellus. Tässä on käytössä ja pääosassa ilman suhteellisen kosteuden ja lämpötilan anturi KY-015. Merkkivalona on kolmivärinen LED, missä on vilkutus sisäänrakennettuna (KY-034), Puhallinta ohjaa rele KY-019. Pääosassa on kosteus- ja lämpötila-anturi (KY-015). 
     Sarjaan-37 jää käsittelemättä joitakin moduuleita (reed-releitä, infrapuna lähetin/vastaanotin, kaltevuusanturi jne.), joille ehkä (ja toivottavasti) löytyy paikka jossain tulevissa pienissä kokeiluissa.
 
Tämä anturi (KY-015) perustuu elementtiin DHT11. Siitä löytyy netistä varsin paljon tietoa ja käyttöesimerkkejä. Useimmissa ohjelmaesimerkeissä on käytetty ohjelmakirjastoa #include <DHT11.h>. Se ei kuitenkaan kuulu Arduinon peruskirjastoon, enkä onnistunut lataamaan sitä mistään. Tämä puute johti siihen, että tämän kertainen ohjelma perustuu netistä otettuun esimerkkiin, mikä ei käytä tuota kirjastomoduulia. (Itse asiassa, kun tämä ohjelma oli jo valmis, sain lisättyä tuon kirjastotiedoston (DHT11.h) Aduino → libraries – hakemistoon, mutta työn takana oli!) Testaus jää toiseen kertaan.
     On myös hyvä huomata, että signaalipinni (Con_SarjaPinni = 8;), joka astetuksissa määritellään lähdöksi (OUTPUT), muutetaan pääohjelmassa välillä tuloksi ja välillä lähdöksi. Toisin sanoen tässä anturissa on ainoastaan yksi pinni, josta sekä käynnistetään että luetaan sarjamuotoista dataa.

DHT11 sarjasihnaali.
 
Ensimmäiseksi Arduino (MCU) lähettää käynnistyssignaalin. Pienen tauon jälkeen anturi (DHT) lähettää 40 bitin datan, mikä koostuu seuraavasti: kosteuden kokonaisosa (8bit), kosteuden desimaaliosa (8bit), lämpötilan kokonaisosa (8bit) sekä lämpötilan desimaaliosa (8bit). Näiden neljän perään vielä kahdeksanbittinen tarkistussumma, joka koostuu noiden neljän edellisen summasta.
Anturin ominaisuudet on ilmoitettu varsin selkeästi.


 Mitta-alueet ja tarkuus. Ne ovat aivan riittävät tähän tarkoitukseen. Netin esittelyistä käy myös selvästi ilmi, että anturi (DHT11) kalibroidaan tehtaalla valmiiksi noihin arvoin.
  
Moduuli KY-015 osoitti kyllä jotain aivan muuta. Se ilmoitti ilman kosteudeksi 20%. Se ei voinut pitää paikkaansa, sillä tuo kuivuus tuntuisi jo hengityksessä. Huoneessa oleva hiushydrometri osoitti 47%. Selvä ristiriita siis oli olemassa, mikä tietysti piti selvittää. Lämpötila näytti kyllä samaa toisen sisälämpömittarin kanssa. Myös desimaalit puuttuivat. Ne puuttuivat myös alkuperäisellä esimerkkiohjelmalla toteutettuna. Hylkäsin desimaalit, sillä noilla tarkkuuksilla niillä ei olisi suurtakaan merkitystä. 
 
Ensin piti selvittää todellinen huoneen suhteellinen kosteus. Mitä kuivempaa, sitä enemmän kosteus haihtuu. Tähän fysiikan lakiin perustuu perinteinen suhteellisen kosteuden mittaus. Mitataan kuivan anturin lämpötila ja märän anturin lämpötila. Koska haihtuminen vaatii energiaa, ei sitä ole saatavissa muulla tavoin kuin laskemalla lämpötilaa. Mittausjärjestely on viereisessä kuvassa.
 
Kuvassa on vesiastia (huoneenläpöistä vettä), puhallin ja kaksi lämpötila-anturia. Platina-100-anturit (Pt-100) ovat yleisempiä, mutta nämä ovat Nikkeli-100 (Ni-100), koska sellaisia sattui olemaan. Toinen anturi on peitetty sideharsolla, ja sen alahelma kostuu vedessä. Puhallin puhaltaa molempiin antureihin ilmaa, joten kuiva anturi on huoneen lämpötilainen ja märkä jäähtyy haihdutuksesta johtuen. Ohessa Ni-100 anturin vastusarvot huoneen lämpötilan ympäristössä:
Vastusmuutos näillä antureilla on niin pieni, että ilman vahvistimia niitä on hankala kytkeä Arduinoon, mutta vastus on helppo mitata yleismittarilla sekä katsoa lämpötiloja vastaavat arvot taulukosta. Saatujen arvojen muuttaminen suhteelliseksi kosteudeksi onnistuu käyrästön avulla:


Tuota käyrästöä piti pienentää niin pajon,. Että sen käyttö lienee hankalaa tällä sivulla, mutta seuraavasta osoitteesta se on käytettävissä kokonaisena:
 
Kuiva anturi oli 112,9ohm, vastaten 22,7ºC ja märkä anturi 108,4ohm, vastaten 15,1ºC. Tällä lämpötilaerolla huoneen suhteellinen kosteus on siis noin 45%.
      En tiedä, johtuiko anturin (KY-015) laadusta vai mistä, mutta se tietysti näytti, kuten mainitsin, aivan väärin. Siksi ohjelmassa on anturista saatuun arvoon lisäys, jolloin lukema näytössä on kuta kuinkin oikein. Ihmiselle suositeltava suhteellinen kosteus liikkuu siellä 35 ja 60%:in välillä. Anturin signaali kyllä muuttui kosteuden muuttuessa. Ilmeisesti vain pohja-arvo oli kalibroimatta tehtaalla?


Tuossa vailla kostutusosaa ja seuraavassa kuvassa kostutuksen kanssa.
 
Puhallin puhaltaa ilmaa märän "vanun" läpi anturille.
  
Ohjelmassa ohjataan kosteusanturin lisäksi relettä ja LEDiä. Releyksikkö (KY-019) ja vilkulla varustettua LEDi- (KY-034) ohjataan yksinkertaisella ON-OFF-säädöllä. Kun kosteus laskee tietyn rajan alle, puhallin käynnistyy ja LEDi alkaa vilkkua. Kun ylempi raja ylittyy, pysähtyy ohjaus ja ohjelma alkaa odottaa kosteustiedon alenemista. Tämä toistuu automaattisesti. Videosta (antinarduvideo32.youtube.com) saa jonkinlaisen käsityksen toiminnasta. Se ei kuitenkaan ole kestoltaan niin pitkä, että siinä näkyisi kostutus- ja odotusvaiheet.
  
Kevät ja pakkaset ovat tulossa. Huoneiden sisäilman sopivan kosteuden ylläpitämiseksi tarvitaan toki huomattavasti tehokkaampi kostutin kuin tämä. Hyviä pakkaspäiviä!
 
OHJELMA 32
/***************************************
* Ohjelma 32
* 4.1.2017
* KOSTUTUSKESKUS
**************************************/

// MÄÄRITTELYT:
// LCD-näyttö
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4,3,2);
const byteapi = B11100001; // ä
const byte opi = B11101111; // ö
const byte ast = B11011111; // aste

// Perusmäärityksiä
const int Con_SarjaPinni = 8;
const int Con_Rele = 6;
const int Con_LEDPinni = 7;
byte int _data [5];

// ALIOHJELMAT
// Datan luku
byte Fun_LueData () {
byte data;
    for (int i = 0; i < 8; i ++) {
      if (digitalRead (Con_SarjaPinni) == false) {
         while (digitalRead (Con_SarjaPinni) == false); // Odotus, kun 0 (false)
            delayMicroseconds (30); // Odotetaan keskelle pulssia
             if (digitalRead (Con_SarjaPinni) == true)
         data |= (1 << (7-i)); // Jos erisuuri, bittisiirto vasemmalle
      while (digitalRead (Con_SarjaPinni) == true); // Jos 1 (true), odottaa seuraavaa bittiä
}
}
return data; // Palautettava arvo
} // Luennan loppu

// Anturin ohjaus ja luenta
void Fun_SarjaIO() {
    digitalWrite (Con_SarjaPinni, false); // Käynnistyssignaali
    delay (30); // Viive pidempi kuin 18ms,DHT11 tunnistaa käynnistyksen
    digitalWrite (Con_SarjaPinni, true);
    delayMicroseconds(40); // Odotetaan vastausta
    pinMode (Con_SarjaPinni, INPUT); // Asetetaan tuloksi
    while (digitalRead (Con_SarjaPinni) == true);
       delayMicroseconds(80); // DHT11 vastaus 80us aikana
       if (digitalRead (Con_SarjaPinni) == false);
         delayMicroseconds(80);
// Vastaanotetaan lämpötila ja kosteusdata. Tarkistusbittiä ei huomioida.
    for (int i = 0; i < 4; i ++)
      Byt_data[i] = Fun_LueData ();
      pinMode (Con_SarjaPinni, OUTPUT); // Asetetaan lähdöksi
          digitalWrite (Con_SarjaPinni, true); // Vapautusbitti seuraavaa luentaa varten
}// Ohjausfunktio loppu

// ASETUKSET:
void setup(){
    Serial.begin(9600);
    lcd.begin(20,2);
    lcd.clear();
// Tekstit näyttöön, suoritetaan kerran
    lcd.setCursor(0, 0);
    lcd.print("Suht.kosteus: %");
    lcd.setCursor(0, 1);
    lcd.print(" L");
    lcd.write(api);
    lcd.print("mp");
    lcd.write(opi);
    lcd.print("tila: ");
    lcd.write(ast);
    lcd.print("C");
// IO-pinnien määrittely
    pinMode(Con_SarjaPinni, OUTPUT);
    pinMode(Con_LEDPinni, OUTPUT);
    pinMode(Con_Rele, OUTPUT);
}// Asetuksen loppu

// PÄÄLOOPPI
void loop(){
    Fun_SarjaIO ();
        lcd.setCursor(14, 0);
        lcd.print(Byt_data [0]+23, DEC);
// lcd.print(".");
// lcd.print(Byt_data [1], DEC);
        lcd.setCursor(14, 1);
        lcd.print(Byt_data [2], DEC);
// lcd.print(".");
// lcd.print(Byt_data [3], DEC);
        if(Byt_data [0] + 15 < 40){
           digitalWrite (Con_LEDPinni, HIGH);
           digitalWrite (Con_Rele, HIGH);
        }else if(Byt_data [0] + 23 > 47) {
           digitalWrite (Con_LEDPinni, LOW);
           digitalWrite (Con_Rele, LOW);
}// ohjaus loppu

delay(1000);
} // Pääohjelma LOPPU
 

torstai 29. joulukuuta 2016

”TerveysTutka”, versio 0.0

Aikamoinen johtohässäkkä.
Tässä sovelluksessa on käytössä moduulit KY-039 (sydämen lyönnin indikaattori) ja KY-013 (termistorilla toteutettu lämpötila-anturi). Näyttönä on SUNTAI SC.2002A2BLYY-W kaksirivinen ja 20 merkkipaikkainen LCD-näyttö. Pääasia ei tietenkään ole terveyden tarkkailu, vaan pieni ohjelmasovellus näihin ihmisen ja moduuleiden rajapintaan. Esimerkki sydämen sykinnästä on videolla antinarduvideo31.youtube.com.
Tuo LCD-näyttö on jo varsin vanhaa ja poistuvaa tyyppiä. Sitä on kuitenkin jäänyt roikkumaan Yleiselektroniikka Oy:n hyllylle. Jäljellä olevat näytöt myydään pilkkahintaan. Kannattaa hyödyntää ne pois. Ei kirpaise niin pahasti, jos tuhoutuu kokeilussa.
 
Tämä LCD-näyttömalli ei sisällä ”ääkkösiä”, joten ne on ladattava kirjastoon (#LiquidCrystal.h) erikseen. Pienet ä ja ö sisältyvän tämän näytön kirjastoon, samoin kuin asteen merkki, mutta eivät kuitenkaan ole aivan samat, mitä ASCII-taulukko osoittaa. Suuret Ä ja Ö pitää muodostaa ja ladata aivan erikseen, piste pisteeltä, kuten myöskin ”sydämen imu- ja puristusvaiheet”. Noita isoja ääkkösiä ei tässä tekstissä tarvita.
     Tuo lämpötila-anturi (KY-013) perustuu NTC-termistoriin (NTC = Negative Temperature Coefficient = negatiivinen lämpötilakerroin), minkä vastus siis pienenee lämpötilan kasvaessa. Vastuksen muutos EI ole lineaarinen lämpötilan muutokseen nähden. Tässä sovelluksessa mittausta EI ole linearisoitu. Ohjelmassa on mittaus ja näyttö kalibroitu kahdessa pisteessä: 1. mittausta verrattu huoneen lämpötilaan ja säädetty näyttämään samaa. 2. Anturilla on mitattu ihon lämpötila = ”kainalokalibrointi” (37C). On mahdollista, että nuo pisteet ovat melko tarkkoja, mutta muut lämpötilat heittävät enemmän tai vähemmän.
     Anturissa KY-039 on IR-LEDi osoittamassa valotransistoria (tässä myös IR-alueelle herkkä) kohti. Infrapunasäteily tunkeutuu näiden kahden komponentin väliin laitetun sormen läpi. Veren erilainen määrä sormen päässä muuttaa säteilyn intensiteettiä, joten mooduulin lähdöstä on saatavissa vastaava signaali. Valotransistoriin vaikuttaa myös ympäristö. Siksi sovelluksessa on kaksi kondensaattoria, joilla pitkittäin olevalla erotetaan anturin antama tasajännite (pohjalukema) sekä hitaat muutokset. (Ohjelmallisesti en onnistunut sauodatuksessa.) Poikittain oleva pienempi kondensaattori suodattaa nopeat, esim. huoneen valaistuksen ym. häriöt. Sen rinnalla on 100 kilo-ohmin purkausvastus. Aikavakiot sopivat sydämen lyöntirytmiin. Täten on mahdollista saada sydämen kuvio sykkimään lyöntien tahtiin. Signaali on varsin pieni – muutaman kymmenen millivoltin luokkaa. Onneksi kuitenkin ja luotan vakaasti, että SOTE-porukalla tulee olemaan oleellisesti korkeatasoisemmat tutkimusvälineen kuin tässä esittämäni.
Moduulien ja LCD-näytön kytkentä Arduinoon
Ohjeman aluksi määritellään ja tuodaan kirjasto: #include <LiquidCrystal.h>. Seuraavana kirjastomoduulille kerrotaan ohjausbitit. Ne eivät välttämättä tarvitse olla juuri nuo, mutta niiden järjestys on oleellinen. Sitten kirjastolle määritellään binääritietoina ä, ö ja aste. Ne löytyvät näytön ominaisuuksista. Sitten kaksi kahdeksan (8) bitin binääritaulukkoa, joilla kirjastolle määritellään sydämen näytön kaksi tilaa.
     Näiden jälkeen muiden muuttujien määrittelyt ja aliohjelmat. Asetuksissa kirjoitetaan vakiotekstit, ja täten vain kerran. Sydänkuvio, lyöntinopeus sekä lämpötila päivitetään omille kohdilleen ohjelman aikana.
     Ohjelman aluksi mitataan sisäistä aikaa (millis()) ja lasketaan lämpötila. Sydämen lyönnin valvonta koostuu kolmen askeleen sekvenssistä. Sekvenssi odottaa sykettä askeleessa 1. Kun sellainen havaitaan analogiatulossa, talletetaan tämä aikalukema askeleessa 1 ja siirrytään askeleeseen 2 odottamaan uutta sykäystä. Kun se on tullut, talletetaan sykkeiden väliaika ja hypätään askeleeseen 3. Siellä lasketaan ja muunnetaan väliaika sydämenlyönneiksi minuutissa, sekä päivitetään tämä ja lämpötilan lukema näyttöön. Sekvenssi hyppää takaisin askeleeseen yksi odottamaan seuraavaa lyöntiä.
Viimeisenä ohjelmassa on muutama lause, joilla pienen viiveen jälkeen palauttetaan sydänkuvio ”imutilaan”.

OHJELMA 31
/**************************************
* Ohjelma 31
* 28.12.2016
* Kehon lämpötila ja sydänpulssit
**************************************/
// MÄÄRITTELYT:
   #include <LiquidCrystal.h>
   LiquidCrystal lcd(12, 11, 5, 4,3,2);

   const byte api = B11100001; // ä
   const byte opi = B11101111; // ö
   const byte ast = B11011111; // aste
   byte Syd1[8] = // Sydämen reunat
                           {B00000,B00000,B01010,B10101,
                           B10001,B10001,B01010,B00100};
   byte Syd2[8] = // Sydämen sisus
                           {B00000,B00000,B00000,B01010,
                           B01110,B01110,B00100,B00000};
   int i = 0;

// Ajastukset
   unsigned long Ulo_MilliSek = 0;
   unsigned long Ulo_EdellinenMilliSek = 0;
   const int Con_Kierrokset = 40;
   unsigned int Unt_Kierrokset = 0;
   int Int_Kierrokset = 0;
   boolean Bol_Aika = false;

// Sydän-määritykset
   const int Con_Pulssi = A1;
   int Int_PulssiRaaka = 0;
   unsigned int Unt_SykeVali = 0;
   float Flo_PulsPerMin = 0;
   unsigned int Unt_Pulssit = 0;
   boolean Bol_SydSyk = false;
   int Seq_Syke = 1;

// Lämpötilan mittaus
   const int Con_LampAnturi = A0;
   int Int_LampoRaaka = 0;
   float Flo_LampoTila = 0.0;

// ALIOHJELMAT
// Sydämen syke näyttöön
   void Fun_Sydan(int i){
      lcd.setCursor(10,0);
      lcd.print(" ");
      lcd.write(i);
   }// Syke loppu

// Pulssit näyttöön
   void Fun_Pulssit(int p){
      lcd.setCursor(13,0);
     lcd.print(p);
   }

//Lämpötila näyttöön
   void Fun_Lampo(float t){
      lcd.setCursor(11,1);
      lcd.print(t);
      lcd.setCursor(15,1);
      lcd.write(ast);
      lcd.print("C");
   }

// ASETUKSET:
   void setup(){
      Serial.begin(9600); // Sarjaliikenteen alustus
      lcd.createChar(0,Syd1); // Sydämen reunat
      lcd.createChar(1,Syd2); // Sydämen sisus
      lcd.begin(20,2);
      lcd.clear();
// Tekstit näyttöön, suoritetaan kerran
      lcd.setCursor(0, 0);
      lcd.print("Syd");
      lcd.write(api);
      lcd.print("nly");
      lcd.write(opi);
      lcd.print("n:         p/min");
      lcd.setCursor(0, 1);
      lcd.print("L");
      lcd.write(api);
      lcd.print("mp");
      lcd.write(opi);
      lcd.print("tila:");
   }// Asetuksen loppu

// PÄÄLOOPPI
   void loop(){
      Ulo_MilliSek = millis();
      Int_LampoRaaka = analogRead(Con_LampAnturi);
      int Temp = constrain((335 - Int_LampoRaaka), 0, 100);
      Flo_LampoTila = 22.7 + 0.17 * Temp;
      Int_PulssiRaaka = analogRead(Con_Pulssi);
      if(Int_PulssiRaaka > 2){
            Bol_SydSyk = true;
            if(Seq_Syke == 1) {Bol_SydSyk = true;}
      } // Pulssin mittaus loppu

// Sydän supistuu
      switch (Seq_Syke) {
            case 1:
                  if(Bol_SydSyk == true){
                        Ulo_EdellinenMilliSek = Ulo_MilliSek;
                        Bol_SydSyk = false;
                        delay(300);
                  Seq_Syke = 2;
                  }
            break;
            case 2:
                  if(Int_PulssiRaaka > 2){
                        Unt_SykeVali = Ulo_MilliSek - Ulo_EdellinenMilliSek;
                        Unt_SykeVali = constrain(Unt_SykeVali, 10, 2999);
                        Bol_SydSyk = false;
                  if(Unt_SykeVali < 3000){
                        Seq_Syke = 3;
                  }}
            break;
            case 3:
                  Flo_PulsPerMin = 1000.0 / Unt_SykeVali * 60.0;
                  Unt_Pulssit = Flo_PulsPerMin;
                  Unt_Pulssit = constrain(Unt_Pulssit, 20, 99);
                  i = 1;
                        Fun_Sydan(i);
                        Fun_Pulssit(Unt_Pulssit);
                        Bol_SydSyk = false;
                        Fun_Lampo(Flo_LampoTila);
                  Seq_Syke = 1;
                  break;
            }// Sekvenssi loppu

// Sydän avautuu
      Unt_Kierrokset++;
      if(Unt_Kierrokset > Con_Kierrokset){
            Bol_SydSyk = false;
            i = 0;
            Fun_Sydan(i);
            Unt_Kierrokset = 0;} // Kierroslaskuri loppu

      delay(1);
} // Pääohjelma LOPPU