//
/* 
Steuerung Western-Lok mit ATtiny85
(Zugset "Classic Train" von Kaufland um 2015)
www.gheinz.de vom 3.5.2021

Funktionen:
  - Drehzahlstellung des Antriebsmotors
  - langsames Anfahren/Halten mit Magnetsensor (Zauberstab)
  - Soundkarte der Lok schalten
  - Kesselfeuer-LED rot flackern
  
für Arduino-Compiler 1.8.9 installieren:
  Datei/Voreinstellungen/Zusätzliche Bordverwalter-URLs: 
  "githubusercontent.com/damellis/attiny/ide-1.6.x-boards-manager/package_damellis_attiny_index.json"
  Werkzeuge/Board/Boardverwalter:
  "attiny by David A. Mellis Version 1.0.2"

Hochladen mit Arduino v.1.8.9:  nicht möglich, Ursache unklar
Einstellungen:
  Board:  "ATtiny 25/45/85"
  Prozessor: "ATtiny85"
  Clock: "Internal 8 MHz" (bei Auslieferung ATtiny)
  AVR-ISP mkII (USBprog):
  -> Fehler: Driver wird erkannt (VID und PID) aber trotzdem geht nichts.
  USBasp:
  ->Fehler: Driver wird erkannt (VID und PID) aber trotzdem geht nichts.
  
Hochladen mit Arduino v.1.0.4:  - ok -
Einstellungen:
  Board:  "Digispark (Tiny Core)"
  Programmer: "USBasp"
  vid=0x16c0 pid=0x5dc vendor www.fischl.de  avrdude USBasp
  (verwendet libusb-win32)
  Zum Hochladen wird der Prozessor auf einen Programmieradapter gesteckt 
  -> DIL8 in IC-Fassung (GND, VCC, MOSI, MISO, SCK, /RST)
  
Arduino-Pins:
  0: PWM0 an Motor
  1: PWM1 Kessel-LED mit Flackerlicht
  2: Ein/Aus Soundgenerator mit Bug-LED 
  3: Speed-Poti abfragen (analogRead)
  4: Reed-Kontakt Start/Stop abfragen
  
          ___________________
         | o                 |
D5, A0 --|PB5_/RST        VCC|-- +5Volt
D3, A3 --|PB3         SCK_PB2|-- D2, A1
D4, A2 --|PB4        MISO_PB1|-- D1, PWM1
    0V --|GND        MOSI_PB0|-- D0, PWM0
         |___________________|
      
Achtung: analogRead() Bug:
  D2 is analogRead(1); 
  D4 is analogRead(2); 
  D3 is analogRead(3);     // der stimmt zufällig, den nehmen wir
  D5 is analogRead(0);  

Sonstiges:
  Stromaufnahme bei Stillstand 7,5 mA, bei Vmax 200 mA
  Binäre Sketchgröße: 2.420 Bytes (von einem Maximum von 6.012 Bytes)
  gesamte Zeit für Compilation und Hochladen: 7 Sekunden (!) auf Intel-Atom winXP 1GHz
*/
  
// Pins am Digispark Tiny85
#define motorpin   0      // Gateanschluß des Power-MOSFET für Lokmotor (PWM0)
#define ledpin     1      // Ofen-LED Flackerlicht (PWM1)
#define soundpin   2      // Soundgenerator mit Frontal-LED
#define potipin    3      // Potentiometer zur Geschwindigkeitseinstellung
#define reedpin    4      // 2 Reedschalter unter dem Dach und unter dem Boden

// Pulsweite mit PowerMOS IRF3205
#define pwmmin     30     // PWM-Intervall 0...255; kann begrenzt werden
#define pwmmax     255   // von pwmmin bis pwmmax
uint8_t pwm;  // aktuelle PWM-Stellung
int potival;  // Stellung des Potis, 0...1023
int zeit;     // Zeitlauf
int geschw;   // Geschwindigkeit
bool reedval; // Wert am Reedpin
bool sound;   // Sound ein/aus
bool start;   // Anfahren
uint8_t phase;    // Bewegungsphase 1...4

// portx can be: DDRB, PORTB, INPB ... while bitx is PB0, PB1 ...
#define bset(portx,bitx) ((portx) |= (1 << (bitx)))
#define bclr(portx,bitx) ((portx) &= (unsigned) ~(1 << (bitx)))
#define btgl(portx,bitx) ((portx) ^= (1 << (bitx)))

void init_pins() {
  pinMode(motorpin, OUTPUT);           // Motor-PWM Ausgang
  pinMode(ledpin, OUTPUT);             // Ofen-Licht Ausgang
  pinMode(soundpin, OUTPUT);           // Soundgenerator Ausgang
 // pinMode(potipin, INPUT);           // Potentiometer analog
  pinMode(reedpin, INPUT);             // (INPUT_PULLUP nicht unter dieser Arduino-Version)
  // set INPUT_PULLUP for reedpin PB4 nachträglich (ginge wohl auch):
  // bclr(DDRB, PB4);     // input
  // bset(PORTB, PB4);    // pullup
  // leider schon vorher pullup-Widerstand 22k eingelötet
}

void ventilsim() {    // Kessel-Überdruckventil akustisch simulieren
  digitalWrite(soundpin, HIGH); delay(1); digitalWrite(soundpin, LOW);
  delay(10);
  digitalWrite(soundpin, HIGH); delay(2); digitalWrite(soundpin, LOW);
}

// scale-function out=scale(in,...) ranges: _min, _max
long scale(long in, long in_min, long in_max, long out_min, long out_max) {
  return ((in - in_min) * (out_max - out_min) / (in_max - in_min) + out_min);
}    // out = scale(inp, fromL, fromH, toL, toH)

///////////////////////////////////////////////////////////////////////////////

void setup() {                
  init_pins();
  analogWrite(motorpin, 0);            // Motor Stop
  phase = 1;    // Vorheizen
  zeit = 0;
  start = 0;
}

///////////////////////////////////////////////////////////////////////////////

void loop() {
  
  // Phasen: 1 Vorheizen, 2 Anfahren, 3 Fahren, 4 Anhalten
  
  // Vorheizen ###########################################
  while (phase == 1) {  // Kessellicht flackert, Ventilsimulation akustisch
    int rand = random(100,255); analogWrite(ledpin, rand);  // Kessel heizen  
    zeit++; delay(5);   // alle paar Takte zweimal rascheln und vorn blinken
    if ( (zeit%50) == 0 ) {    // wenn zeit dividiert durch 50 den Rest 0 hat -> alle 50
      ventilsim(); // Überdruckventil akustisch simulieren
    }
    // Sensor abfragen
    while ( (bool) digitalRead(reedpin) == 0) {   // Magnetsensor löste aus
      zeit = 0;
      start = 1;
      digitalWrite(soundpin, HIGH); // ein, als Erkennungszeichen
      int rand = random(100,255); analogWrite(ledpin, rand);  // Kessel heizen
    }   // verläßt erst dann die Schleife, wenn reedpin wieder HIGH ist
    if (start == 1) {  // Startsignal erkannt
      // digitalWrite(soundpin, LOW);  // aus
      if (zeit >= 10) {     // nach paar Millisekunden
        geschw = 0;
        potival = analogRead(potipin);  // potipin ablesen, return 0...1023
        phase = 2;           // -> Anfahren
      }
    }
  }
  
  // Anfahren ###########################################
  while (phase == 2) {
    int rand = random(100,255); analogWrite(ledpin, rand);  // Kessel heizen  
    zeit++; delay(1);   // Zeit läuft in ms-Taktung
    digitalWrite(soundpin, HIGH); // ein -> Lärm machen
    // langsam anfahren
    if (geschw <= potival) {    // langsam anfahren bis zur max. Geschwindigkeit (vom Poti vorgegeben)
      pwm = (uint8_t) scale(geschw,0,1023,pwmmin,pwmmax);  // skalieren
      analogWrite(motorpin, pwm);         // 0...255, PWM mit 490 Hz
      geschw++;
    }
    else {    // Endgeschwindigkeit erreicht
      phase = 3;      // dort geht es weiter
    }
    // Sensor abfragen
    while ( (bool) digitalRead(reedpin) == 0) {   // Magnetsensor löste aus
      phase = 4;      // -> anhalten
    }
  }
  
  // Fahren mit Höchstgeschwindigkeit ####################
  while (phase == 3) {     
    int rand = random(100,255); analogWrite(ledpin, rand);  // Kessel heizen
    zeit++; delay(5);   // Zeit läuft in 5 ms-Taktung
    // Sensor abfragen
    while ( (bool) digitalRead(reedpin) == 0) {   // Magnetsensor löste aus
      phase = 4;      // -> anhalten
    }
  }

  // Anhalten ###########################################
  while (phase == 4) { 
    // int rand = random(20,120); analogWrite(ledpin, rand);  // Kessel heizen
    // langsamer werden
    while (geschw > 0) {
      zeit++; delay(1);   // Zeit läuft in ms-Taktung
      if ( (zeit%30) == 0 ) {    // wenn zeit dividiert durch 30 den Rest 0 hat -> alle 30 Takte
        ventilsim(); // Überdruckventil akustisch simulieren
      }
      pwm = (uint8_t) scale(geschw,0,1023,0,pwmmax);  // skalieren
      analogWrite(motorpin, pwm);         // 0...255, PWM mit 490 Hz 
      int rand = random(10,50); analogWrite(ledpin, rand);  // Kessel heizen
      geschw--;
      geschw--;
      digitalWrite(soundpin, LOW);  // aus
    }
    // reset all
    zeit = 0;
    start = 0;
    geschw = 0;
    analogWrite(motorpin, 0);
    phase = 1;    // -> in Vorheiz-Phase zurückspringen
  }
}

//