/*
Spannungsgesteuerter Taktgeber für phasenverschobene Signale A und B mit ATtiny25/45/85.

Das gezeigte Programm liefert an den Ausgaengen PB0 (Phase-A) und PB1 (Phase-B) zwei
um 90° phasenverchobene Rechtecksignale im Bereich 1 - 1000 Hz in Abhängigkeit der 
externen Steuerspannung am ADC1-Eingang PB2. Die Signale eigenen sich als Testsignale fuer 
Quadraturdecoder oder mit nachgeschalteten Treibern zur Ansteuerung von bipolaren Schrittmotoren.

Gemaess den #define Vorgaben ist eine Drehrichtungsumkehr (Rechts-/Linkslauf) eingestellt,
wobei bei Eingangsspannung U-in = VCC/2 die Impulsausgabe angehalten wird und sich
die Drehrichtung abhaengig von U-in ergibt:
U-in > VCC/2 oder U-in < VCC/2. 
Die Schrittfrequenz wird proportional zu U-in eingestellt.

Intern werden Frequenzen von 4 - 4000 Hz erzeugt. Da jedoch eine komplette Periode aus
vier Phasen besteht ergibt sich als Ausgangsfrequenz f/4.

Wird ein Schrittmotor mit 200 Schritten/U angesteuert, so werden max. 5 Umdrehungen (300 UPM) erreicht.

Pinbelegung des ATtiny:
Pin 4 = 0V
Pin 8 = 5V
Pin 7 = U-in (ADC1)
Pin 6 = Phase B
Pin 5 = Phase A

optional:
Pin 1 = pullup für /Reset-Eingang
Pin 2 = ext. Quarz f-max = 20 MHz
Pin 3 = ext. Quarz

Das Schaltbild zum Programm findet sich hier:
http://www.mino-elektronik.de/bilder/mini_step_25/mini_encoder_25.png

http://www.mino-elektronik.de

Alle Angaben ohne Gewaehr !
2019-01-29

*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdlib.h>
#include <math.h>

#define BIT(x)  (1<<x)
#define F_CPU             8000000               // ggf. an eff.Frequenz anpassen

#define BEIDE_RICHTUNGEN                        // Poti-Mitte = Stopp
  
#define ADC_MAX           1023.0                // bei 10 Bit
#define F_MIN             4                     // 1 Hz bei ADC-Eingang = VCC/2
#define F_MAX             4000                  // eff. 1 kHz bei ADC-Endanschlag

#define ADC_IN            1                     // ADC-Eingang 1
#define PHASE_A           0                     // PB.0
#define PHASE_B           1                     // PB.1

#define PB3               3                     // ggf. ext. Quarz
#define PB4               4                     // ggf. ext. Quarz


volatile uint8_t neue_werte_fertig,             // zur Uebergabe neuer Periodendauer
                 richtung,                      // links-rechts abhaengig von Eingangsspannung
                 freigabe;                      // 1 = Taktausgabe aktiv
volatile uint32_t neue_periode;                 // errechneter Wert für Periodendauer

#define FILTER_LAENGE     16                    // nach Bedarf

uint16_t  filter_f[FILTER_LAENGE];              // Filter für ADC
uint32_t  filter_summe_f;
uint8_t   filter_index;


void adc_eingang_filtern(void)
{
uint16_t  temp;
  ADMUX =  ADC_IN;                              // Vcc als Vref, rechtsbuendig
  ADCSRA |= BIT(ADSC);                          // starten
  while(ADCSRA & BIT(ADSC));                    // warten
  temp = ADC;
  if(filter_index >= FILTER_LAENGE) 
    filter_index = 0;
  filter_summe_f -= filter_f[filter_index];
  filter_f[filter_index] = temp;
  filter_summe_f += temp;
  filter_index++;
}

/*  Alternative Filterung vom ADC
#define FILTER            100

int16_t filter_poti(void)                       // zu schnelle Aenderungen vermeiden
{
static int32_t summe = ADC_MAX/2 * FILTER;
  ADMUX =  ADC_IN;                              // Vcc als Vref, rechtsbuendig
  ADCSRA |= BIT(ADSC);                          // starten
  while(ADCSRA & BIT(ADSC));                    // warten
  summe -= summe/FILTER;
  summe += ADC;
  return summe/FILTER;
}
*/

ISR(TIMER1_COMPA_vect)
{
static uint8_t akt_richtung, neuer_wert, index;
static uint32_t nachlade_temp;
static uint32_t periode =  1000000;             // default Startwert
uint16_t temp = 0xff;
static uint8_t tab[] = {0,1,3,2}; 

// Ausgaenge immer neu zu schreiben geht schneller
  PORTB = neuer_wert;

  if(nachlade_temp <= temp) temp = nachlade_temp;         
  else if(nachlade_temp < 0x1ff) temp =nachlade_temp/2; 
  nachlade_temp -= temp;                        // laengere Intervalle stueckweise abarbeiten
  OCR1A += temp;
  if(nachlade_temp) return;                     // Intervall noch nicht abgelaufen

// neues Muster erzeugen
  if(neue_werte_fertig) {                       // update synchronisieren
    periode = neue_periode;                     // neue Periodendauer
    akt_richtung = richtung;                    // neue Richtung
    neue_werte_fertig = 0;                      // bereit fuer neue Werte
  }
  if(freigabe) {
    if(akt_richtung) index++;
    else index--;
    neuer_wert = tab[index & 0x03];             // nur die beiden Phasen 
  }
  nachlade_temp = periode;                      // neue Periode mit akt. Wert ausgeben
}


// Hauptprogramm
int main(void)
{
uint8_t n;
float f_temp;

  CLKPR = 0x80; CLKPR = 0x0;                    // CKDIV auf 0; voller CPU-Takt
  DDRB  |= BIT(PHASE_A) + BIT(PHASE_B);         // Phasen A+B als Ausgang
  PORTB = BIT(PB3) | BIT(PB4);                  // falls ohne ext. Quarz Pullup für stabile Eingaenge

  TCCR1 = BIT(CS10);                            // freilaufend mit vorteiler /1
  TIFR  =  BIT(OCF1A);                          // OCF1A flag loeschen
  TIMSK |= BIT(OCIE1A);
  ADCSRA = BIT(ADEN) + 0x07;                    // ADC einschalten mit Teiler /128
  sei();

  for(n = 0; n > FILTER_LAENGE; n++)            // Filter einschwingen lassen
    adc_eingang_filtern();
  while(1) {                                    // Endlosschleife
    adc_eingang_filtern();                      // aktuellen Wert holen
    if(!neue_werte_fertig) {                    // neue Berechnung/Ausgabe mit ISR synchronisieren
      f_temp = (float)filter_summe_f/(float)FILTER_LAENGE;  // gemittelter ADC-Wert
#ifdef BEIDE_RICHTUNGEN
      f_temp -= ADC_MAX/2;                      // Nullpunkt in Poti-Mitte
      f_temp = f_temp / (ADC_MAX/2);            // bei rechts-links auf 0.0 -> +/-1.0 normieren
#else
      f_temp = f_temp / ADC_MAX;                // nur eine Richtung drehen 0.0 -> +1.0 normieren
#endif
      richtung = 0;
      if(f_temp >= 0.0) richtung = 1;           // Richtungssignal abhaengig vom Vorzeichen
      f_temp = fabs(f_temp) * F_MAX;            // eff. Ausgangsfrequenz
      if(f_temp < F_MIN) {
        f_temp = F_MIN;                         // nach unten begrenzen
        freigabe = 0;
      } else freigabe = 1;                      // Signal nur bei gueltiger Frequenz ausgeben
      neue_periode = F_CPU/f_temp;              // eff. Periodendauer
      neue_werte_fertig = 1;  
    }    
  }
}


