/*
Spannungsgesteuerter Taktgeber für Schrittmotore mit ATtiny25/45/85.

Das gezeigte Programm liefert Takt- und Richtungsimpuls für einen Schrittmotortreiber
im Bereich 50 Hz - 50 kHz in Abhängigkeit der externen Steuerspannung am ADC1-Eingang.

Gemaess den #define Vorgaben ist Rechts-/Linkslauf eingestellt, wobei bei Eingangsspannung
U-in = VCC/2 der Motor 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.


Wird der Schrittmotor mit 1/16 Mikroschritt betrieben, ergibt sich bei einem typischen Motor 
mit 200 Vollschritten pro Umdrehung die maximale Drehzahl aus 50000/(200*16) = 16,625 Umdrehungen/s oder
937,5 U/min. Schnellere Drehzahlen koennten durch 1/4 oder 1/8 Mikroschrittbetrieb eingestellt werden, wobei
geprueft werden muss, ob Motor, Mechanik und Schrittmotortreiber noch Spielraum für höhere Drehzahlen bieten.

Pinbelegung des ATtiny:
Pin 4 = 0V
Pin 8 = 5V
Pin 7 = U-in (ADC1)
Pin 6 = Taktausgang
Pin 5 = Richtungsausgang

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_step_25.png

http://www.mino-elektronik.de

Alle Angaben ohne Gewaehr !
2013-12-02

*/
#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
                                                // max. Ausgangsfrequenz: 55 kHz @ 8 MHz F_CPU

#define BEIDE_RICHTUNGEN                        // Rechts-/Linkslauf; Poti-Mitte = Stopp
  
#define ADC_MAX           1023.0                // bei 10 Bit
#define F_MIN             50                    // 50 Hz bei ADC-Eingang = VCC/2
#define F_MAX             50.0e3                // 50 kHz bei ADC-Endanschlag

#define ADC_IN            1                     // ADC-Eingang 1
#define STEP              1                     // STEP an PB.1  
#define DIR               0                     // DIR an PB.0  

#define PB3               3
#define PB4               4

#define FILTER_LAENGE     16                    // nach Bedarf

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

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++;
}

ISR(TIMER1_COMPA_vect)
{
static uint8_t akt_richtung;
static uint32_t nachlade_temp;
static uint32_t periode =  2000;                // default Startwert
uint16_t temp = 0xff;

  TCCR1 &= ~BIT(COM1A0);                        // OC1A loeschen falls gesetzt
  GTCCR = BIT(FOC1A);                           // per Force Output Compare erzwingen

  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

  if(akt_richtung) PORTB |= BIT(DIR);           // Richtungssignal ausgeben
  else PORTB &= ~BIT(DIR);                      // und
  if(freigabe) TCCR1 |= BIT(COM1A0);            // beim nächsten Interrupt OC1A synchron setzen
  if(neue_werte_fertig) {                       // update synchronisieren
    periode = neue_periode;                     // neue Periodendauer
    akt_richtung = richtung;                    // neue Richtung
    neue_werte_fertig = 0;                      // bereit fuer neue Werte
  }
  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(STEP) + BIT(DIR);                // STEP, DIR Ausgang
  PORTB = BIT(PB3) | BIT(PB4);                  // Pullup für stabile Eingaenge

  TCCR1 = BIT(CS10)| BIT(COM1A1);               // 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;  
    }    
  }
}


