keskiviikko 16. marraskuuta 2016

Latausvalvoja

Minulla on käytössä kesämökillä aurinkopaneeleita. Yhtä paneelia valvoo Arduinolla toteutettu laite, mikä katkaisee akun latauksen, kun jännite nousee 14,5 voltiin. Periaatteessa aivan oikein, mutta valvoja sijaisee lähellä paneelia, jolloin se ei ota huomioon syöttöjohdossa syntyvää jännitehäviötä. Toisin sanoen, mitä paremmin aurinko paistaa, niin sitä suurempi on latausvirta, ja sitä aikaisemmin valvoja katkaisee latauksen akulta. Näin ei pitäisi olla.

Varsinkin silloin, jos käytössä on seurantalaite, eli paneeli seuraa aurinkon kulkua, ja valvonta on lähellä paneelia, on tärkeätä, että tuo mainittu jännitehäviö otetaan huomioon. On mahdollista vetää toiset johdot pelkästään jännitteen mittausta varten suoraan akun navoista, mutta siihen en halunnut ryhtyä. Toinen tapa on mitata virta juuri ennen katkaisua, tallettaa sen arvo ja välittömästi sen jälkeen mitata jännitepudotus. Välittömästi siksi, että akun napajännite alkaa pudota nopeasti latauksen loputtua. Ohmin laki U = I * R (jännite on virran ja vastuksen tulo) tarjoaa tarpeellisen evään ongelman ratkaisuun. Mainituilla mittauksilla voidaan johtojen vastus laskea R = U / I (jännitepudotus jaetaan talletetulla virralla, jolloin saadaan esille johtojen vastus). Jatkossa mitataan latausvirtaa ja lisätään virran ja lasketun vastuksen tulo tuohon 14,5 voltin katkaisujännitteeseen Näin yksinkertaista se on!

Tuo oli luonnollisesti ajatus ja käsitys ennen kuin aloin toteuttaa po. valvontalaitetta. Miten mitata? Arduinon analogiatulot ovat 0 .. 5VDC ja 10 bitin muunnos (lukulue 0 .. 1023). Toisin sanoen uusi lukema saadaan 5mV:in välein (5000mV / 1024 = 4,88mV). Latausjännite ei kuitenkaan suoraan käy analogiamittaukseen, koska se on paljon suurempi kuin 5V. Kytkennässä olevilla arvoilla (5,6kohm ja 17,7kohm) koko mitta-alueeksi tulee 20.8V. Toisin sanoen uusi jännitteen lukema saadaankin tällä kytkennällä noin 20mV välein. Sekin on hyvä ja aivan riittävä tarkkuus tähän tarkoitukseen.

Virran mittaukseen ajattelin ensin shunttivastusta. Toisin sanoen pieniohmista tehovastusta, joka kestää latausvirran vioittumatta. Tässä tapauksessa 0,125 ohmia (sellaisia oli laatikossa). Se ei voi olla kovin suuri, sillä latausvirran pitää päätyä akkuun eikä lämmittää tätä mittausvastusta. 5 ampeerin virralla (noin 100W paneeli) tuon mittausvastuksen yli vaikuttaa siten 5A * 0,125ohm = 0,625V. Jos shuttivastus sijoittetaan plus-johtoon, vaikuttaa siihen myös latauslinjan jännite. Tällöin shunttivastuksen molemmista päistä voidaan tehdä analogiamittaus ja muuntaa näin saatu jännite-ero latausvirraksi. Koska yli 5voltin jännitettä ei voida kytkeä suoraan analogiamittaukseen, tarvitaan ym. jännitteenjakovastukset. Tuolla järjestelyllä pääsisi melkein 3% resoluutioon, mutta hylkäsin tämänkin, sillä kuvittelin pääseväni parempaan tarkkuuteen sijoittamalla shuntin miinusjohtoon, jolloin tuo 0,625V voidaan kytkeä suoraan analogiatuloon. Tällöin resoluutio olisi hiukan prosenttia parempi. Näin kuvittelin. Ratkaisu ei toiminut, vaikka sama toimii kuvassa taustalla näkyvässä vanhassa teholähteessä, minkä alkuperäiset viisarimittarit lopettivat toimintansa. Se oli sopiva sovelluskohde Arduinolle. Ilmeisesti (?) ongelma johtui siitä, että miinus ja maapotentiaali eivät olleet enää sama piste.

Hall-generaattoreita "edestä ja takaa". Paksut johdot ovat päävirtaa varten ja ohuet signaalia varten.
 
Tasavirtaa voidaan mitata myös Hall-generaattorilla. Se on puolijohdekomponentti, mikä pystyy muuttamaan magneettikentän voimakkuuden analogiajännitteeksi. Pienin löytämäni Hall-generaattori on tyyppiä ACS758LCB-050B ja se on maksimissaa 50A:in virralle. Siis tarpeettoman suuri tähän käyttöön, mutta sillä lähdin eteenpäin. Sen herkkyys on 40mV/A. Jos ajatellaan tätä 5A:in mittausaluetta, on komponentista saatava jännite 200mV (0,2V). Tämä jännite voidaan kytkeä suoraan analogiatuloon, joten muunnosportaita syntyy noin 40kpl ja tarkkuus jää hiukan alle 2%. Tähän tarkoitukseen tämäkin tarkkuus on aivan riittävä. Hall-generaattorin lähtö (siis 0-virralla) ei ole kuitenkaan nolla, sillä maan ja muu ympäröivä magneettikenttä aiheuttaa pohjalukeman. Tämä hallitaan siten, että nollavirralla talletetaan Hall-generaattorin lukema, ja tämä luku vähennetään varsinaisesta mittausarvosta.

Tuo oli teoriaosuus. Käytäntö ei ollut lainkaan yksinkertaista ja suoraviivaista. Ison virheen tein ensinnäkin siinä, että ensimmäiseen versioon juotin johdot Hall-generaattorin virtapuolelle. Se on aika paksua kuparia ja kupari johtaa hyvin lämpöä, joten jotain vioittui puolijohdepiirissä. Lukemat olivat stabiileita ja järkeviä, niin kauan kun Arduinon syöttö tapahtui USB-liittimen kautta tietokoneesta, mutta heti syötön siirryttyä akulle, lukemat heittelivät miten sattui. Syöttöjännitteessä oli pientä häiriötä, mutta se ei edellyttänyt moisia heittoja. Vasta viimeiseksi epäilin Hall-generaattoria, joten teettikin paljon töitä, kun olin eliminoinut muut kuvittelemani virhelähteet pois. Uudella generaattorilla, mihin kytkin virtajohdot ruuviliitoksin, mitään tuollaista epämääräisyyttä ei ilmennyt. Johtuen yllä mainitusta, on tämän ohjelman versionumero 2.4. Aina kun pääsin testeissäni johonkin välitavoitteeseen, otin uuden versionumeron, jolloin helposti pääsi palaamaan takaisin edelliseen toimivaan toteutukseen ja kopioimaan niistä toimivia osuuksia. (Ei noita versioita kuitenkaan 24 tullut, vaan isomman ponnistuksen jälkeen siirryin uudelle kymmenluvulle.)

Oikealla oleva vastussarja simuloi johtovastusta
Se oli vain yksi virheläde. Kovin stabiilia mittausta tästä ei kuitenkaan ole mahdollista saada. Siksi ohjelmassa käytetään jossakin mittauksessa keskiarvolaskentaa (mitataan useita peräkkäisiä mittauksia ja lasketaan niiden keskiarvo) sekä suodatusta (talletetaan edellinen mitausarvo, otetaan siitä 90% ja uudesta mittauksesta 10%). Yksi melkoinen hankaluus on jännitteen mittaus juuri latauksen katkon jälkeen. Ohjelmassa olevat arvot ovat pitkälti tulosta kokeilusta ja järkevään lopputulokseen pyrkimisestä. Voin sanoa, että toiminta ei ole kovinkaan tarkkaa, mutta oikean suuntaista. Ensimmäisellä kerralla katkaisu tapahtuu 14,5 voltin arvolla ja seuraavilla kerroilla siihen lisätään saatu johtojen häviöjännite, mikä on varmuuden vuoksi rajoitettu alle yhden voltin, jottei akun latausjännite pääse kasvamaan liian suureksi. Sekä liian suuri latausvirta että liian suuri latausjännite pilaavat akun ennen pitkää.

Latausvalvojan kytkentä. Molemmat LEDit ovat samassa kuoressa.
OHJELMASTA
HUOM! Ilmeisesti työkaluohjelman (Arduino) uusimmassa versiossa on tapahtunut sellainen muutos, että aliohjelmat eivät voi enää sijaita pääohjelman perässä (mihin oli tottunut). Jos sijaitsevat, ilmoittaa kääntäjä virheestä (aliohjelmaa ei ole määritelty). Tässä ohjelmassa aliohjelmat (funktiot) on sijoitettu asetuksen (setup()) ja pääohjelman väliin. Tämä merkitsee myös sitä, että aiemmin täällä julkistamani ohjelmaesimerkit eivät toimi sellaisenaan, vaan funktiot on siirrettävä pääohjelman eteen.

Asetuksen lopussa talletetaan ensimmäisen kerran Hall-generaattorin pohjalukema. Koska tämä osio suoritetaan ainoastaan kerran, olen käyttänyt yksinkertaista viivettä (delay(ms)).

Analogiamittaukset (jännite ja virta) sekä kaksivärisen LEDin (punainen ja vihreä) sekä latausreleen ohjaus toteutetaan funktioina. Analogiapuolen aliohjelmat palauttavat liukuluvun. Siksi niiden tyyppi on float (eikä void).
      Pääohjelmassa on ensimmäisenä kierroslaskuri, millä simuloidaan viiveaikoja. Ajan tarkkuus ei kuitenkaan ole kovin tähdellistä. Latauskatkon jälkeinen tauko on noin 5 minuuttia. Muuttujat ovat tyyppiä etumerkitön kokonaisluku (unsigned int), joten niistä ei koskaan tule negatiivisiä lukuja. Jos lukualue ( 0 .. 65536) ylittyy, alkaa uusi laskenta nollasta. Aikalukema on siis aina positiivinen luku.
      Seuraavana latauksen pysäytys- ja käynnistyskytkimien käsittely (kytkin on kolmiasentoinen). Pysäytyskytkin pysyy asennossaan, joten lataus voidaan kytkeä tarvittaessa pois päältä. Käynnistyskytkin on palautuva ja lataus alkaa vivun palautuessa keskiasentoon. Pysäytyskytkimen (kuvassa irti) toiminnan merkkinä punainen LED palaa.
      Seuraavana on varsinainen latauksen valvontasekvenssi. Ensimmäisessä askeleessa aurinkopaneeli on kytketty akkuun ja lataus käynnistyy. Ensimmäisellä kerralla lataus katkaistaan 14,5 voltin arvolla ja seuraavilla kerroilla tuohon lisätään jännite, mikä saadaan kertomalla sen hetkinen latausvirta lasketulla syöttöjohtojen vastusarvolla. Tässä askeleessa vihreä LED palaa.
      Toisessa askeleessa mitataan lyhyen viiveen (delay(3)) jälkeen välitön jännitepudotus latauksen loputtua. Tässä kerätään viisi peräkkäistä mittausta ja lasketaan niiden keskiarvo. Tämä on jännite, mikä latauksen aikana hävisi johtoihin.
      Kolmannessa askeleessa lasketaan johtojen vastus (plus- ja miinus-johto yhteensä).
      Neljännessä askeleessa odotetaan noin viisi minuuttia ennen uutta latausta. Merkkinä askeleesta on punainen vilkku. Punainen LED vaihtaa tilaansa, jos kierrosten lukumäärä (const int Con_Kierrokset = 800;) on saavutettu, tai on saavutettu puolet siitä. Vilkutusaika on näin ollen 0,5s päällä ja 0,5s pois.
      Viidennessä askeleessa odotetaan, että akun jännite on laskenut (14,5V – 1,0V) sille tasolle, että uusi lataus voidaan aloittaa. Merkkinä tästä askeleesta on vihreä vilkku. Jos samoihin akkuihin on kytketty useampia paneeleita, voi olla, että ne ylläpitävät akun jännitettä tuon rajan yläpuolella. Lopuksi askeleessa päivitettään Hall-generaattorin pohja-arvo ja hypätään askeleeseen yksi uutta latauskierrosta varten.


/***************************************
* PaneeliValvonta_v24
* Antti Isännäinen
* 09.11.2016
* Valvonta käsi (haarukka) kääntimeen
**************************************/
/* Tähtäimenä paneelivalvoja, mikä ottaa huomioon siirtojohtojen
* jännitehäviön. Käynnistyksen jälkeen lataus loppuu jännite-
* arvolla 14,5V. Seuraavilla kerroilla mitataan jännitepudotus
* latauksen loppuessa ja lasketaan siirtojohtojen lenkkivastus.
* Seuraavilla kerroilla rajajännitteeeseen lisätään (14,5 + häviö)
* siirtojohtoihin häviävä, (virta * vastus = jännite) jännite.
*
* Versiossa 2.1 Jouduin jälleen lähtöruutuun, sillä jännitepudotuksen
* mittaus latauksen katkon jälkeen ei edellisessä versiossa (1.2)
* ollu riittävän luotettava.
*
* Versiossa 2.2 (v22) on pohjana melko luotettava latauskatkon
* jälkeinen jännitemittaus ja suodatus. Lyhyen viiveen (10 ms)
* muodostetaan viiden (5) mittauksen keskiarvo.
* Tässä versiossa on myös virran mittaus riittävän luotettava.
*
* Versiossa 2.3 (v23) jatkuu varsinaisen paneelivalvojan
* kehitys. Pohjana versio 1.2 (v12).
*
* Versiossa 2.4 (v24) on jännittteiden, virtojen, johtovastuksen
* ja näistä lasketun lisäjännitteen (katkaisupiste) arvot toteutuneet
* käytännössä riittävällä tarkuudella.
*/

// MÄÄRITTELYT:
// Ajastukset
const int Con_Kierrokset = 800;
unsigned int Unt_Sekunnit = 0; // Viivelaskuri
unsigned int Unt_Kierrokset = 0; // Ohjelmakierroslaskuri
int Int_Kierrokset = 0;
// Jännite
const int Con_AnaTuloV = 0; // Tulopinni A0
int Int_AnaRaakaV = 0; // Muunnosarvo
float Flo_Jannite = 0; // Jännitteen mittaus
float Flo_EdellJannite = 0;
float Flo_JannSuod = 0;
float Flo_JannMuisti = 0;
float Flo_JohtoSumma = 0;
// Virta
const int Con_AnaTuloI = 2; // Tulopinni A2
int Int_AnaRaakaI = 0; // Muunnosarvo
int Int_PohjaArvo = 0; // Hall-gen. pohjalukema luetaan asetuksissa
float Flo_Virta = 0;
float Flo_VirtaSuod = 0;
float Flo_EdellVirta = 0;
float Flo_VirtaMuisti = 0;
// Digitaalimäärittelyt
// Tulot
const int Con_Seis = 4; // Kytkin pysyvästi ylös
boolean Bol_Seis = false;
int Int_Seis_Viive = 5; // Kytkinvärähtelyn suodatus
int Seq_Seis = 1; // Kytkimen lukusekvenssi
const int Con_Start = 5; // Kytkin alas palautuva
boolean Bol_Start = false;
int Int_Start_Viive = 5;
int Seq_Start = 1;
// Lähdöt
const int Con_LEDpun = 8;
boolean Bol_LED_Pun = false;
const int Con_LEDvih = 9;
boolean Bol_LED_Vih = false;
const int Con_Rele = 10;
boolean Bol_Rele = false;

// Valvontasekvenssi
const float Con_Irti_Raja = 14.5; // Akun napojen rajajännite
float Flo_JohtoJannite = 0.0;
float Flo_SilmukkaVastus = 0.0;
int Seq_Valvonta = 0; // Valvontasekvenssi

void setup() {
Serial.begin(9600);
pinMode(Con_Seis, INPUT);
pinMode(Con_Start, INPUT);
pinMode(Con_LEDpun, OUTPUT);
pinMode(Con_LEDvih, OUTPUT);
pinMode(Con_Rele, OUTPUT);
digitalWrite(Con_Rele, true); delay(300); // Irroitetaan lataus
Int_PohjaArvo = analogRead(Con_AnaTuloI); // Alustetaan Hall-pohja-arvo
digitalWrite(Con_Rele, false); delay(100); // kytketään lataus
}// Asetusten loppu

// ALIOHJELMAT
// ANALOGIA ALIOHJELMAT
// Jännitteen käsittely
float Fun_MittaaV(){
Int_AnaRaakaV = analogRead(Con_AnaTuloV);
return Int_AnaRaakaV / 50.6;
}// Jännitteen mittaus loppu

// Virran käsittely
float Fun_MittaaI(){
Int_AnaRaakaI = analogRead(Con_AnaTuloI);
Int_AnaRaakaI = Int_AnaRaakaI - Int_PohjaArvo;
Int_AnaRaakaI = constrain(Int_AnaRaakaI, 0, 100);
return Int_AnaRaakaI / 8.2;
}// Virran mittaus loppu

// OHJAUSTEN ALIOHJELMAT
void Fun_LEDvih(boolean Tila){
digitalWrite(Con_LEDvih, Tila);
}// Vihreän LEDin ohjauksen loppu
void Fun_LEDpun(boolean Tila){
digitalWrite(Con_LEDpun, Tila);
}// Punaisen LEDin ohjauksen loppu
void Fun_Rele(boolean Tila){
digitalWrite(Con_Rele, Tila);
}// Irroitusreleen ohjauksen loppu


//PÄÄOHJELMA
void loop() {
// Ohjelmakierros / viivelaskuri
Unt_Kierrokset++;
if(Unt_Kierrokset > Con_Kierrokset){ // Luvulla sovitetaan kierr -> sek
Unt_Sekunnit++;
Unt_Kierrokset = 0;
} // Kierroslakuri loppu

// Kytkintietojen käsittely
// Seis-kytkin
Bol_Seis = digitalRead(Con_Seis); // Luetaan kytkimen tila
switch (Seq_Seis) {
case 1:
if(Bol_Seis == true){
Int_Seis_Viive --;
if(Int_Seis_Viive == 0){
Int_Seis_Viive = 5;
Seq_Valvonta = 0;
Fun_LEDvih(false);
Seq_Seis = 2;
}
}
break;
case 2:
Fun_LEDpun(true);
Fun_Rele(true);
if(Bol_Seis == false){
Fun_LEDpun(false);
Seq_Seis = 1;
}
break;
} // Seis kytkinsekvenssi loppu

// Start-kykin
Bol_Start = digitalRead(Con_Start); // Luetaan kytkimen tila
switch (Seq_Start) {
case 1:
if(Bol_Start == true){
Int_Start_Viive --;
if(Int_Start_Viive == 0){
Int_Start_Viive = 5;
Seq_Start = 2;
}
}
break;
case 2:
if(Bol_Start == false){
Int_PohjaArvo = analogRead(Con_AnaTuloI);
Seq_Valvonta = 1;
Seq_Start = 1;
}
break;
} // Start kytkinsekvenssi loppu

// Akun latauksen valvontasekvenssi
switch (Seq_Valvonta) {
case 1: // Latausaskel
Fun_LEDvih(true);
Fun_Rele(false); // rele päästää, lataus alkaa
Flo_Jannite = Fun_MittaaV();
Flo_JannSuod = Flo_EdellJannite * 0.9 + Flo_Jannite * 0.1;
Flo_EdellJannite = Flo_JannSuod;
Flo_Virta = Fun_MittaaI();
Flo_VirtaSuod = Flo_EdellVirta * 0.9 + Flo_Virta *0.1;
Flo_EdellVirta = Flo_VirtaSuod;
if(Flo_JannSuod > Con_Irti_Raja + Flo_SilmukkaVastus * Flo_VirtaSuod){
Flo_JannMuisti = Flo_JannSuod;
Flo_VirtaMuisti = Flo_EdellVirta;
Fun_Rele(true); // rele vetää, lataus katkaistaan
Unt_Sekunnit = 0; // Nollataan "sekunnit"
Fun_LEDvih(false);
Seq_Valvonta = 2;
}
break;
case 2: // Jännitepudotuksen mittaus
delay(3); // 3 ms stabilointiviive
for (int iV = 0; iV < 5; iV++){ // Mitataan 5 peräkkäistä arvoa
Flo_Jannite = Fun_MittaaV();
Flo_JohtoSumma = Flo_JohtoSumma + Flo_Jannite;
} // for-silmukan loppu
Flo_Jannite = Flo_JohtoSumma / 5; // Muodostetaan viiden keskiarvo
Flo_JohtoSumma = 0; // Nollataan muisti seuraavaa kierrosta varten
Flo_JohtoJannite = Flo_JannMuisti - Flo_Jannite;
Flo_JohtoJannite = constrain(Flo_JohtoJannite, 0.0, 1.0);
Seq_Valvonta = 3;
break;
case 3: // Johtovastuksen laskenta
Flo_SilmukkaVastus = Flo_JohtoJannite / Flo_VirtaMuisti;
Seq_Valvonta = 4;
break;
case 4: // Purkausviive
if(Unt_Kierrokset == Con_Kierrokset ||
Unt_Kierrokset == Con_Kierrokset / 2){
Bol_LED_Pun = !Bol_LED_Pun;}
Fun_LEDpun(Bol_LED_Pun);
if(Unt_Sekunnit > 300){
Fun_LEDpun(false);
Seq_Valvonta = 5;
}
break;
case 5: // Latauksen palautus
if(Unt_Kierrokset == Con_Kierrokset ||
Unt_Kierrokset == Con_Kierrokset / 2){
Bol_LED_Vih = !Bol_LED_Vih;}
Fun_LEDvih(Bol_LED_Vih);
Flo_Jannite = Fun_MittaaV();
Flo_JannSuod = Flo_EdellJannite * 0.9 + Flo_Jannite * 0.1;
Flo_EdellJannite = Flo_JannSuod;
if(Flo_JannSuod < Con_Irti_Raja - 1.0){
Int_PohjaArvo = analogRead(Con_AnaTuloI); // Alustetaan Hall-pohja-arvo
Fun_LEDvih(false);
Seq_Valvonta = 1;}
break;
} // Valvontasekvenssi loppu

delay(1);
} // Pääohjelman loppu


lauantai 3. syyskuuta 2016

Automaattinen sähkötysavain

Radioamatööri – lehden lokakuun numerossa on artikkelini Arduinolla toteutetusta automaattisesta sähkötysavaimesta. Artikkelissa käsitellään ainoastaan muutamia ohjelmatoteutuksen kohtia, sillä ohjelma olisi ollut turhan pitkä lehtiartikkeliin. Tässä on viimeisin ohjelmaversio. Tämä on vapaasti käytettävissa ja halutessa myös muutettavissa. Rivit 63, 64 ja 65 (sijaisevat asetusosassa) on joka tapauksessa muutettava vastaamaan kunkin radioamatöörin tarpeita. (Noissa on vakiolauseet esim. yleiskutsua, nimeä ja lopetusta varten.)






 

/* Nimi: CW_Test_16v21
Luotu: 11.2.2016
Luoja: Antti Isännäinen

KUVAUS:
Automaattiavain amatöörikäyttöön. Viiva vasemmalle, piste oikealle.
Lisäksi painikkeilla voi valita kolme valmista sanomaa esim: yleiskutsu,
nimi ja QTH sekä yhteyden lopetus kiitoksineen.
Nämä ovat vapaasti valittavissa, mutta ainoastaan ohjelmointivaiheessa
kirjoitettavissa selvätekstinä ja ladatavissa ohjelman käännöksen ja
latauksen yhteydessä. Ääkköset olen jättänyt pois, koska niillä ei ole
yhden merkin ASCII-koodia. Nekin on saatavissa sähkötysmerkeiksi, mutta
tekstissä toisilla merkeillä.
Sähkötysnopeus on säädettävissä potentiometrillä. Samoin suuriohmisille
kuulokkeille tarkoitettu myötäkuuntelun voimakkuus.
Kuunteluäänen korkeus on vakio, mutta määriteltävissä ohjelmointivaiheessa.

Versiossa 10 ensimmäistä kertaa oikea avain käytössä.

Espoossa 5.2.2016
Versio 20 käsittää lopullisen hardiksen, eli itetaidetta
Tyydyttävä toiminta versiolla 20.
Huomioi, ettei lähetyksen nopeuitta voi muuttaa kesken sanoman,
ainoastaan sanomien välillä. Käsin sähkötettäessä nopeutta voi muuttaa
merkkien välillä.

Espoossa 11.2.2016
Versiossa 21 muutetaan tauko hiukan pistettä lyhemmäksi, koska
se netin mukaan on elegantimpaa.
*/
// MÄÄRITTELYT:
int Int_Taajuus = 750; // Myötäkuuntelun taajuus
int Int_Nopeus = 0;
int Int_Piste = 0; // Pisteen kesto viiva/3
int Int_Vali = 0; // Merkkiväli Hiukan lyhempi kuin piste
// int Int_SanaVali = 3*Int_Nopeus;
int Seq_Sanoma = 0;
int p = 3; // piste
int v = 1; // viiva
int i = 0;
int j = 0;
int k = 0;

// Yksittäisten merkkien taulukot; ensin merkin pituus, sitten merkit
int A[] ={2,p,v,0,0,0};int B[] ={4,v,p,p,p,0};int C[] ={4,v,p,v,p,0};
int D[] ={3,v,p,p,0,0};int E[] ={1,p,0,0,0,0};int F[] ={4,p,p,v,p,0};
int G[] ={3,v,v,p,0,0};int H[] ={4,p,p,p,p,0};int I[] ={2,p,p,0,0,0};
int J[] ={4,p,v,v,v,0};int K[] ={3,v,p,v,0,0};int L[] ={4,p,v,p,p,0};
int M[] ={2,v,v,0,0,0};int N[] ={2,v,p,0,0,0};int O[] ={3,v,v,v,0,0};
int P[] ={4,p,v,v,p,0};int Q[] ={4,v,v,p,v,0};int R[] ={3,p,v,p,0,0};
int S[] ={3,p,p,p,0,0};int T[] ={1,v,0,0,0,0};int U[] ={3,p,p,v,0,0};
int V[] ={4,p,p,p,v,0};int W[] ={3,p,v,v,0,0};int X[] ={4,v,p,p,v,0};
int Y[] ={4,v,p,v,v,0};int Z[] ={4,v,v,p,p,0}; // 90 ASCII
int nol[] ={5,v,v,v,v,v}; int yks[] ={5,p,v,v,v,v}; int kak[] ={5,p,p,v,v,v};
int kol[] ={5,p,p,p,v,v}; int nel[] ={5,p,p,p,p,v}; int vii[] ={5,p,p,p,p,p};
int kuu[] ={5,v,p,p,p,p}; int sei[] ={5,v,v,p,p,p}; int kah[] ={5,v,v,v,p,p};
int yhd[] ={5,v,v,v,v,p};

int Arr_Merkki[6]; int Arr_ABC[36][6]; // Merkin ja merkkien välitaulukot

char Arr_Lause1[] = {"CQ CQ DE OH2WJ OH3YD OH6KVE PSE K "};
char Arr_Lause2[] = {"MY NAME ANTTI ANTTI ES QTH ESPOO QTH ESPOO "};
char Arr_Lause3[] = {"TNX FER QSO 73 73 GL ES CUAGN "};
int Int_Pituus1 = 0; int Int_Pituus2 = 0; int Int_Pituus3 = 0;
int Int_Merkki = 0; int Int_AsciiEro = 0;

// Liitynnät
const int Con_Key = 3;
const int Con_LED = 4;
const int Con_Aani = 5;
const int Con_Txt1 = 6; boolean Bol_Txt1 = false;
const int Con_LedPois = 7; boolean Bol_LedPois = false;
const int Con_Txt2 = 8; boolean Bol_Txt2 = false;
const int Con_RelePois = 9; boolean Bol_RelePois = false;
const int Con_Txt3 = 10; boolean Bol_Txt3 = false;
const int Con_Viiva = 11; boolean Bol_Viiva = false;
const int Con_Piste = 12; boolean Bol_Piste = false;
const int Con_AnaTulo = 0; int Int_AnaTulo = 0;

// ASETUKSET:
void setup(){ // Alustetaan muuttujat sekä määritellään I/O-tyyppi
Serial.begin(9600); // Alustetaan sarjaliikennne testiä varten
//while (!Serial) { ; } // Odottaa sarjaportin kytkeytymistä
pinMode(Con_Key, OUTPUT);
pinMode(Con_LED, OUTPUT);
pinMode(Con_Txt1, INPUT);
pinMode(Con_Txt2, INPUT);
pinMode(Con_Txt3, INPUT);
pinMode(Con_Viiva, INPUT);
pinMode(Con_Piste, INPUT);
pinMode(Con_LedPois, INPUT);
pinMode(Con_RelePois, INPUT);
// Ladataan kaksiulotteinen merkkitaulukko. Tarvitsee tehdä vain käynnistettäessä
for(i = 0; i < 6; i++){Arr_ABC[0][i] = A[i];} for(i = 0; i < 6; i++){Arr_ABC[1][i] = B[i];}
for(i = 0; i < 6; i++){Arr_ABC[2][i] = C[i];} for(i = 0; i < 6; i++){Arr_ABC[3][i] = D[i];}
for(i = 0; i < 6; i++){Arr_ABC[4][i] = E[i];} for(i = 0; i < 6; i++){Arr_ABC[5][i] = F[i];}
for(i = 0; i < 6; i++){Arr_ABC[6][i] = G[i];} for(i = 0; i < 6; i++){Arr_ABC[7][i] = H[i];}
for(i = 0; i < 6; i++){Arr_ABC[8][i] = I[i];} for(i = 0; i < 6; i++){Arr_ABC[9][i] = J[i];}
for(i = 0; i < 6; i++){Arr_ABC[10][i] = K[i];} for(i = 0; i < 6; i++){Arr_ABC[11][i] = L[i];}
for(i = 0; i < 6; i++){Arr_ABC[12][i] = M[i];} for(i = 0; i < 6; i++){Arr_ABC[13][i] = N[i];}
for(i = 0; i < 6; i++){Arr_ABC[14][i] = O[i];} for(i = 0; i < 6; i++){Arr_ABC[15][i] = P[i];}
for(i = 0; i < 6; i++){Arr_ABC[16][i] = Q[i];} for(i = 0; i < 6; i++){Arr_ABC[17][i] = R[i];}
for(i = 0; i < 6; i++){Arr_ABC[18][i] = S[i];} for(i = 0; i < 6; i++){Arr_ABC[19][i] = T[i];}
for(i = 0; i < 6; i++){Arr_ABC[20][i] = U[i];} for(i = 0; i < 6; i++){Arr_ABC[21][i] = V[i];}
for(i = 0; i < 6; i++){Arr_ABC[22][i] = W[i];} for(i = 0; i < 6; i++){Arr_ABC[23][i] = X[i];}
for(i = 0; i < 6; i++){Arr_ABC[24][i] = Y[i];} for(i = 0; i < 6; i++){Arr_ABC[25][i] = Z[i];}
for(i = 0; i < 6; i++){Arr_ABC[26][i] = nol[i];} for(i = 0; i < 6; i++){Arr_ABC[27][i] = yks[i];}
for(i = 0; i < 6; i++){Arr_ABC[28][i] = kak[i];} for(i = 0; i < 6; i++){Arr_ABC[29][i] = kol[i];}
for(i = 0; i < 6; i++){Arr_ABC[30][i] = nel[i];} for(i = 0; i < 6; i++){Arr_ABC[31][i] = vii[i];}
for(i = 0; i < 6; i++){Arr_ABC[32][i] = kuu[i];} for(i = 0; i < 6; i++){Arr_ABC[33][i] = sei[i];}
for(i = 0; i < 6; i++){Arr_ABC[34][i] = kah[i];} for(i = 0; i < 6; i++){Arr_ABC[35][i] = yhd[i];}
}// Asetuksen loppu

// PÄÄLOOPPI
void loop(){

// Tekstipainikkeiden luku
Bol_Txt1 = digitalRead(Con_Txt1);
Bol_Txt2 = digitalRead(Con_Txt2);
Bol_Txt3 = digitalRead(Con_Txt3);
Bol_Viiva = digitalRead(Con_Viiva);
Bol_Piste = digitalRead(Con_Piste);

// Nopeustiedon luku ja nopeuden käsittely
Int_AnaTulo = analogRead(Con_AnaTulo);
Int_Nopeus = map(Int_AnaTulo, 0, 1023, 100, 600);
//Serial.println(Int_Nopeus);
Int_Piste = Int_Nopeus / 3;
Int_Vali = Int_Piste * 8 / 10;

// Viivan lähetys avaimella
if(Bol_Viiva == true){
tone(Con_Aani, Int_Taajuus);
Bol_LedPois = digitalRead(Con_LedPois);
if(Bol_LedPois == false){ digitalWrite(Con_LED, HIGH);}
Bol_RelePois = digitalRead(Con_RelePois);
if(Bol_RelePois == false){digitalWrite(Con_Key, HIGH);}
delay(Int_Nopeus);
noTone(Con_Aani);
digitalWrite(Con_LED, LOW);
digitalWrite(Con_Key, LOW);
delay(Int_Vali); // merkkiväli
}// Viivan loppu

// Pisteen lähetys avaimella
if(Bol_Piste == true){
tone(Con_Aani, Int_Taajuus);
Bol_LedPois = digitalRead(Con_LedPois);
if(Bol_LedPois == false){ digitalWrite(Con_LED, HIGH);}
Bol_RelePois = digitalRead(Con_RelePois);
if(Bol_RelePois == false){digitalWrite(Con_Key, HIGH);}
delay(Int_Piste);
noTone(Con_Aani);
digitalWrite(Con_LED, LOW);
digitalWrite(Con_Key, LOW);
delay(Int_Vali); // merkkiväli
}// Pisteen loppu

// Sanomapainikkeiden runnistus
if(Bol_Txt1 == true){ Seq_Sanoma = 1;}
if(Bol_Txt2 == true){ Seq_Sanoma = 2;}
if(Bol_Txt3 == true){ Seq_Sanoma = 3;}

// Tekstin valinta
switch (Seq_Sanoma) {
case 1: // Ensimmäinen sanoma
for(k = 0; k < sizeof(Arr_Lause1)-1; k++){
Int_Merkki = Arr_Lause1[k];
if(Int_Merkki == 32){delay(Int_Nopeus * 2); // Tunnistetaan sanaväli (SPACE)
}else if(Int_Merkki < 65){Int_AsciiEro = 22;// Tunistetaan numero
Fun_Merkki(Int_Merkki - Int_AsciiEro);
}else {
Int_AsciiEro = 65; // Loput ovat kirjaimia
Fun_Merkki(Int_Merkki - Int_AsciiEro);}}
Seq_Sanoma = 4;
break;
case 2: // Toinen sanoma
for(k = 0; k < sizeof(Arr_Lause2)-1; k++){
Int_Merkki = Arr_Lause2[k];
if(Int_Merkki == 32){delay(Int_Nopeus * 2); // Tunnistetaan sanaväli (SPACE)
}else if(Int_Merkki < 65){Int_AsciiEro = 22;// Tunistetaan numero
Fun_Merkki(Int_Merkki - Int_AsciiEro);
}else {
Int_AsciiEro = 65; // Loput ovat kirjaimia
Fun_Merkki(Int_Merkki - Int_AsciiEro);}}
Seq_Sanoma = 4;
break;
case 3: // Kolmas sanoma
for(k = 0; k < sizeof(Arr_Lause3)-1; k++){
Int_Merkki = Arr_Lause3[k];
if(Int_Merkki == 32){delay(Int_Nopeus * 2); // Tunnistetaan sanaväli (SPACE)
}else if(Int_Merkki < 65){Int_AsciiEro = 22;// Tunistetaan numero
Fun_Merkki(Int_Merkki - Int_AsciiEro);
}else{
Int_AsciiEro = 65; // Loput ovat kirjaimia
Fun_Merkki(Int_Merkki - Int_AsciiEro);}}
Seq_Sanoma = 4;
break;
case 4: // Sanoman lähetyksen lopetus
Seq_Sanoma = 0;
break;
}
delay(1); // IO-stabilointia varten tämä lyhyt tauko
} // Pääohjelma LOPPU

// Merkkien muuntaminen sähkötykseksi
void Fun_Merkki(int x){
int Purku[6];
for(int i = 0; i < 6; i++){Purku[i] = Arr_ABC[x][i];}
for(int i = 0; i < Purku[0]; i++){
tone(Con_Aani, Int_Taajuus);
Bol_LedPois = digitalRead(Con_LedPois);
if(Bol_LedPois == false){ digitalWrite(Con_LED, HIGH);}
Bol_RelePois = digitalRead(Con_RelePois);
if(Bol_RelePois == false){digitalWrite(Con_Key, HIGH);}
delay(Int_Nopeus/Purku[i+1]);
noTone(Con_Aani);
digitalWrite(Con_LED, LOW);
digitalWrite(Con_Key, LOW);
delay(Int_Vali); // merkkiväli
}
delay(Int_Nopeus - Int_Nopeus/10*4); // Kirjainväli
}// Merkki-funktion loppu











torstai 9. kesäkuuta 2016

”Arpakuutio”

Pyrkimykseni on tällä kerralla valottaa sattumalukukäskyn random(min, max) ja siihen liittyvän randomSeed(parametri) käyttöä sekä sarjana sisään ja rinnan ulos –piirin (74HC595) käyttöä 7-segmenttinäyttöelementin ohjauksessa. Tässä näyttömoduulissa on kahdeksan (8) LEDiä, joista seitsemällä muodostetaan luku 0 .. 9 ja näiden lisäksi piste. Näitä näyttöjä on saatavissa erilaisia ja erikokoisia. Eri tyypit poikkeavat myös kytkennältään. Tässä olen käyttänyt tyyppiä MAN4640A. Ohjaimen ja näyttöpiirin LEDien välissä on vastuspiiri, missä on 8 kpl 330 ohmin vastuksia. Jos tarvitaan useamman numeron näyttöjä, voidaan näitä piirejä kytkeä sarjaan. Arduinosta tarvitaan silti vain kolme lähtöpinniä.
Tällä piirilevyllä on myös sellaisia komponentteja (toinen painike ja kaksivärinen LED), joita en käytä tässä sovelluksessa. Niiden on tarkoitus tulla mukaan seuraavan kerran blogissa. Sähköiset kytkennät näkyvät oheisista kuvista. Tuon Vero-levy-hahmotelman mukaan ei kannata lähteä sellaisenaan kytkemään, sillä se kuuluu enemminkin osastoon: ”etsi viisi virhettä!” On siitä kyllä hyötyäkin, sillä on se enimmäkseen oikein. Paint-ohjelma on vain melko alkeellinen, joten myöhemmät korjaukset sillä on varsin hankalia.






 Asetuksessa olevalla käskyllä randomSeed(analogRead(5)); alustetaan sattumageneraattori. Parametrinä voi olla myös kiinteä luku, jolloin ”sattumasarja” on aina sama numerojono. Tätä voi käyttää testeissä, mutta muuten se ei ole mielekästä. Ohjekirja suosittelee parametriksi jotain vapaana olevaan pinniä. Tässä olen käyttänyt analogiatuloa 5. Vapaassa tulossa ilmenee kohinaa, jolloin lähtöluku on sattumanvarainen ja saatu lukusarja todella perustuu sattumaan. Varsinainen sattumakäsky random(min, max) suorittaa arpomisen. Min on lukualueen minimi ja max on suurin arvottava luku lisättynä yhdellä (1). Siksi tässä on rajana luku seitsemän (7), jolloin saadaan aikaan noppien lukualue. Sattumalukukäskyn arpoma luku voi olla hyvin suuri. Siksi muuttujan tulee olla tyyppiä long.

Käynnistyessään ohjelma alkaa pyörittää myötäpäivään kutakin segmentin (A..F, G poisluettuna) LEDiä peräjälkeen. Pistettä ei polteta lainkaan. Siksi LEDien talukossa oikean puoleisin bitti on nolla (0). Bitit ovat käänteisessä järjestyksessä, koska ensimmäinen taulukon bitti Arr_DataA[0] menee sarjasyötössä ensimmäisenä ja bitti 7 viimeisenä (8 bittiä, taulukon bitit 0 .. 7). Tällöin bitti 7 siirtyy viimeisenä muunnospiiriin ja siirtyy pistettä vastaavaan kohtaan Q0. Vastaava taulukkoluettelo on numeroista.
Kun ohjelma käynnistyy, käynnistyy pyörityssekvenssi, ja kun vasenta painiketta painetaan, pysähtyy pyöritys ja ohjelma hyppää arpomista vastaavaan numerosekvenssin askeleeseen. Kun painike päästetään, pysähtyy numerosekvenssi ja pyörityssekvenssi käynnistyy jälleen. Video antinarduvideo22. youtube.com havainnollistaa tapahtumaa parhaiten. Sekvenssiaskeleissa ladataan kukin taulukon bittijono ensin talukkoon Arr_Data[8], mikä välitetään aliohjelmalle varsinaista näyttöön tulostusta varten.

Pyörityssekvenssin viive käsitellään aliohjelmassa int Fun_Viive(int Viive){, mikä on nyt tyyppiä int (eikä void), koska se palauttaa (return) kokonaisluvun. Sinne välitetään kutsussa viiveen hetkelline arvo, sitä kasvatetaan aliohjelmassa ja palautetaan uusi arvo pääohjelmaan. Pääohjelmassa tutkitaan, onko raja-arvo ylitetty, ja jos on, niin hypätään seuraavaan asekeleeseen.





Ohjelma 22
/***************************************
 *  Ohjelma 22
 *  08.06.2016
 *  Noppa
 **************************************/

// MÄÄRITTELYT:
const int Con_PainVas = 2;
boolean Bol_PainVas = false;
int Seq_PainVas = 1;
const int Con_PainVasVii = 5;
int Int_PainVasVii = 0;

const int Con_DS_Data = 6;        // Sarjadatan syöttö
const int Con_SHCP_Kello = 7;     // Sarjadatan kellotus
const int Con_STCP_Siirto = 8;    // Syöttö rinnanlähtöön

//Sekvenssin pyöritys
int Seq_Pyoritus = 1;
const int Con_PyorViive = 47;
int Int_PyorViive = 0;

// Numerosekvenssi
int Seq_Arpa = 0; // Näyttösekvenssiä ei käynnistetä alustuksessa
long Lng_Arpa = 0;
boolean Arr_Data[8] = {0,0,0,0,0,0,0,0};

// Näytön LEDit pyörityksessä
boolean Arr_DataA[8] = {0,0,0,0,0,0,1,0};
boolean Arr_DataB[8] = {0,0,0,0,0,1,0,0};
boolean Arr_DataC[8] = {0,0,0,0,1,0,0,0};
boolean Arr_DataD[8] = {0,0,0,1,0,0,0,0};
boolean Arr_DataE[8] = {0,0,1,0,0,0,0,0};
boolean Arr_DataF[8] = {1,0,0,0,0,0,0,0};

// Numerot 1 .. 6
boolean Arr_Data1[8] = {0,0,0,0,1,1,0,0};
boolean Arr_Data2[8] = {0,1,1,1,0,1,1,0};
boolean Arr_Data3[8] = {0,1,0,1,1,1,1,0};
boolean Arr_Data4[8] = {1,1,0,0,1,1,0,0};
boolean Arr_Data5[8] = {1,1,0,1,1,0,1,0};
boolean Arr_Data6[8] = {1,1,1,1,1,0,1,0};

// ASETUKSET:
void setup(){                 
 Serial.begin(9600);         
 pinMode(Con_PainVas, INPUT);
 pinMode(Con_DS_Data, OUTPUT);
 pinMode(Con_SHCP_Kello, OUTPUT);
 pinMode(Con_STCP_Siirto, OUTPUT);
 randomSeed(analogRead(5)); // Sattumageneraattorin alustus
}// Asetuksen loppu

// PÄÄLOOPPI 
void loop(){
Lng_Arpa = random(1,7);
Bol_PainVas = digitalRead(Con_PainVas);

// KOSKETTIMEN-tilan käsittely
switch (Seq_PainVas) {
  case 1:
    if (Bol_PainVas == true){
      Int_PainVasVii ++;
        if (Int_PainVasVii > Con_PainVasVii){
          Seq_Pyoritus = 0; // Pysäytetään pyöritys
          Seq_Arpa = Lng_Arpa; // Valitaan näytettävä numero
          Fun_Tyhjenna(); // Tyhjennetään näyttö
          Int_PainVasVii = 0;
          Seq_PainVas = 2;
        }
    }
    break;
  case 2:
    if (Bol_PainVas == false){
          Seq_Arpa = 0; // Pysäytettän numeronäyttö
          Seq_Pyoritus = 1; // Käynnistettän pyöritys
          Seq_PainVas = 1;
    }
    break;
}// Vasen painike loppu

// Pyörityksen sekvenssi
switch (Seq_Pyoritus) {
  case 1:
   for(int i = 0; i < 8; i++){
    Arr_Data[i] = Arr_DataA[i];}
      Fun_Naytto();
      Int_PyorViive = Fun_Viive(Int_PyorViive);
      if (Int_PyorViive > Con_PyorViive){
        Int_PyorViive = 0;
        Seq_Pyoritus = 2;
      }
    break;
  case 2:
   for(int i = 0; i < 8; i++){
    Arr_Data[i] = Arr_DataB[i];}
      Fun_Naytto();
      Int_PyorViive = Fun_Viive(Int_PyorViive);
      if (Int_PyorViive > Con_PyorViive){
        Int_PyorViive = 0;
        Seq_Pyoritus = 3;
      }
    break;
  case 3:
   for(int i = 0; i < 8; i++){
    Arr_Data[i] = Arr_DataC[i];}
      Fun_Naytto();
      Int_PyorViive = Fun_Viive(Int_PyorViive);
      if (Int_PyorViive > Con_PyorViive){
        Int_PyorViive = 0;
        Seq_Pyoritus = 4;
      }
    break;
  case 4:
   for(int i = 0; i < 8; i++){
    Arr_Data[i] = Arr_DataD[i];}
      Fun_Naytto();
      Int_PyorViive = Fun_Viive(Int_PyorViive);
      if (Int_PyorViive > Con_PyorViive){
        Int_PyorViive = 0;
        Seq_Pyoritus = 5;
      }
    break;
  case 5:
   for(int i = 0; i < 8; i++){
    Arr_Data[i] = Arr_DataE[i];}
      Fun_Naytto();
      Int_PyorViive = Fun_Viive(Int_PyorViive);
      if (Int_PyorViive > Con_PyorViive){
        Int_PyorViive = 0;
        Seq_Pyoritus = 6;
      }
    break;
  case 6:
   for(int i = 0; i < 8; i++){
    Arr_Data[i] = Arr_DataF[i];}
      Fun_Naytto();
      Int_PyorViive = Fun_Viive(Int_PyorViive);
      if (Int_PyorViive > Con_PyorViive){
        Int_PyorViive = 0;
        Seq_Pyoritus = 1;
      }
    break;
}// Pyörityksen loppu

// Arvotun numeron sekvenssi
switch (Seq_Arpa) {
  case 1:
   for(int i = 0; i < 8; i++){
    Arr_Data[i] = Arr_Data1[i];}
      Fun_Naytto();
      break;
  case 2:
   for(int i = 0; i < 8; i++){
    Arr_Data[i] = Arr_Data2[i];}
      Fun_Naytto();
    break;
  case 3:
   for(int i = 0; i < 8; i++){
    Arr_Data[i] = Arr_Data3[i];}
      Fun_Naytto();
    break;
  case 4:
   for(int i = 0; i < 8; i++){
    Arr_Data[i] = Arr_Data4[i];}
      Fun_Naytto();
    break;
  case 5:
   for(int i = 0; i < 8; i++){
    Arr_Data[i] = Arr_Data5[i];}
      Fun_Naytto();
    break;
  case 6:
   for(int i = 0; i < 8; i++){
    Arr_Data[i] = Arr_Data6[i];}
      Fun_Naytto();
    break;
}// Numeronäytön loppu
delay(1);
} // Pääohjelma LOPPU

// FUNKTIOT
void Fun_Naytto(){
// Datan siirto sarjamuodossa
for(int i = 0; i < 8; i++){
  digitalWrite(Con_DS_Data, Arr_Data[i]);
  digitalWrite(Con_SHCP_Kello, HIGH);
  digitalWrite(Con_SHCP_Kello, LOW);
}// Sarjakirjoitus loppu

// Sarjadatan siirto rinnakkaisiin lähtöihin
  digitalWrite(Con_STCP_Siirto, HIGH);
  digitalWrite(Con_STCP_Siirto, LOW);
}// Näytön loppu

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

int Fun_Viive(int Viive){
  Viive++;
  return(Viive);

}// Viiveen loppu

torstai 2. kesäkuuta 2016

Haptiikka ja ”valkoinen keppi”

Haptiikka on tuntoaistiin perustuvaa aistimista. Lainaus Wikipediasta: Tuntoaistilla eli kosketustunnolla eli paineaistilla tarkoitetaan aistitoimintoa, jolla aistitaan painetta ja hahmotetaan kosketusta, terävyyttä, pehmeyttä, lämpötilaa, kipua ja kehon asentoja.

Tämä sovellus vaikuttaa lähinnä kipuaistiin, mutta niin hellästi, että kieli tuntee sen lähinnä kutituksena. Kielianturiin syötetty 5V:in pulssi on niin matalajännitteinen ja lyhyt, että se ei tunnu kipuna. Tietysti kokemuksessa täytyy olla yksilöllisiä eroja. Huomasin pöydällä laktrisapaloja, joten keksin kääriä elektrodit lakupalan päihin, joten samalla sai ”palkinnon”, sain suun magiaks’. Oleellinen tavoite on tuoda esiin käskyn pulseIn(Pin, HIGH/ LOW) (= pulse In, In isolla iillä) toiminta. Sen avulla voi suurella resoluutiolla mitata, kauanko digitaalipinni on alhaalla / ylhäällä.
Digitaalitekniikkassa on tähän aistiin liittyvien tuotteiden kehitys voimakkaan kehityksen kohteena. Tähän mennessä olemme voineet keskutella puheella etäällä olevan henkilön kanssa ja nykyään mukana on myös kuva. Siis kuulo ja näköaisti ovat nykytekniikalle jo arkipäivää. Ei varmaankaan (näin kuvittelen) mene enää montakaan vuotta, kun voimme aidon tuntuisesti kätellä toisella mantereella olevaa ystävää ja tehdä virtuaalilasit päässä yhteisen patikkaretken vaikkapa vuoristoon.
Mutta nyt laskeudun takaisin alavalle pinnalle. Verenpainemittari hajosi. Siinä paine muutetaan sähköiseksi signaaliksi pietsosähköisellä elementillä. Tässä elementissä mekaaninen puristus tai vääntö saa aikaan sähköjännitteen elementin napoihin. Arkisempi esimerkki ilmiöstä on sytkäri, missä on useita pietsokiteitä sarjassa (kunkin jännite kytkeytyy myös sarjaan) ja vasaran naksahtaessa elementtipinoon, syntyy kipinä, joka sytyttää kaasun, ja savukkeen voi sytyttää (toivottavasti kuitenkin tuuli sammuttaa liekin sitä ennen, jotta keuhkot säilyvät terveempinä).
Toiminto toimii toisinkin päin. Kun elemettiin tuodaan jännite, muuttaa kide muotoaan. Päivittäin törmäämme (tiedostamatta) ilmiöön esim. kännykän yhteydessä. Niissä on tarkkaa taajuutta ja aikaa muodostamassa värähtelevä kide. Arduinossa se näyttää tekevän 16 000 000 (16MHz) heilahdusta sekunnissa. Tavoittelin tuon mittarin elementin irroittamisella juuri tätä. Tarkoitukseni oli side silmillä ”tutkailla” ultraäänianturilla (HC-SR04) ympäristöä ja viedä näin saatu (jalostuksen jälkeen) signaali kehoon teipattuun pietsoelementtiin, jonka värähtelyn voisi tuntea iholla. Anturi toimi, mutta ei tarpeeksi voimakkaasti nyt käytettävissä olevilla jännitteillä (esim. 5V). Ajatus oli hylättävä. Onneksi muistin kuinka lapsena kielenpäällä kokeiltiin, vieläkö litteässä taskulampun paristossa oli ”virtaa” (oikeasti jännitettä 4,5V). Pitääpä siis hyödyntää kielen herkkää tuntoa.
Tähän mennessä tuo oli vasta ensimmäinen erehdys. Lisää oli tulossa! Ultraäänianturi (tämä tyyppi HC-SR04) toimii siten, että lyhyellä posittivisella pulssilla (OUT) trikataan anturia ja seuraavaksi toinen (IN) pinni asettuu HIGH (1) kaikua vastaavaksi ajaksi. Arduinon sisäinen laskuri päivittyy aikaan verrannollista pulsseita. Pulssiluku on suuri ja muuttujan täytyy olla unsigned long. Ääni kulkee ilmassa 340m sekunnissa. Ultraäänipulssi ottaa siis aikaa mennäkseen ja heijastuakseen takaisin. 

Tämän toiminnon aikana Arduino ei suorita mitään muuta ohjelmaa. Ennen kuin hoksasin tämän, oli kokeilussa kulunut paljon aikaa. Lakupala ehti hyvin liueta loppuun. (Tosin pureskelin sen siinä pähkäillessä.) Alkuperäiseen kuvitteluun nähden ohjelmasta tuli paljon yksinkertaisempi. Kaikki kielianturin pulssitukseen liittyvä laskenta jäi pois. Kaiusta muodostui luonnollinen viive. Sitä pidempi, mitä enemmän edessä on vapaata tilaa. Sekvenssillä hallitaan perusjksoa (noin 2s pulssiväli) ja kaiulla (eli lasketuilla pulsseilla) ohjelman kiertoaikaa. Tällöin kieli saa sykäyksen harvakseltaan, kun edessä on paljon tilaa ja ”sähköiskut” kiihtyvät, kun välimatka esteeseen lyhenee. Huoneissa tämä toimii hyvin, mutta ulkona tarvitaan kyllä muutakin (esim. GPS), tai joutuu helposti eksyksiin.


Ohjelma 21
/***************************************
 *  Ohjelma 21
 *  02.06.2016
 *  Haptiikka ja "valkoinen keppi"
 **************************************/

// Määrittelyt
const int Con_Pulssi = 8;
const int Con_Kaiku = 9;
unsigned long Ulo_Kesto1 = 0;
const int Con_LED13 = 13;
const  int Con_Hapti = 2;
int Seq_Pulssitus = 1;
const int Con_PulssiVali = 30;
int Int_PullsiVali = Con_PulssiVali;


// ASETUKSET:
void setup(){                 
 Serial.begin(9600);         
pinMode(Con_Pulssi, OUTPUT);
pinMode(Con_Kaiku, INPUT);
pinMode(Con_LED13, OUTPUT);
pinMode(Con_Hapti, OUTPUT);
}// Asetuksen loppu

// PÄÄLOOPPI        
void loop(){

// Paluupulssin pituuden mittaus
    digitalWrite(Con_Pulssi, HIGH);
    digitalWrite(Con_Pulssi, LOW);
     Ulo_Kesto1 = pulseIn(Con_Kaiku, HIGH); // Mittausjakso

// Muunnos anturille
switch (Seq_Pulssitus) {
  case 1:
    Int_PullsiVali = Con_PulssiVali;
     Seq_Pulssitus = 2;
    break;
  case 2:
    Int_PullsiVali--;
    if(Int_PullsiVali == 0){
      Fun_Pulssi();
      Seq_Pulssitus = 1;}
    break;
 } // Pulssisekvenssin loppu

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

void Fun_Pulssi(){
  digitalWrite(Con_Hapti, HIGH);
  digitalWrite(Con_LED13, HIGH); 
  delay(30);
  digitalWrite(Con_LED13, LOW);
  digitalWrite(Con_Hapti, LOW); 
} // Aliohjeman loppu