/*
Spannungsgesteuerter Taktgeber für 2 x Schrittmotore mit ATmega48...328

Das gezeigte Programm liefert Taktimpulse für 2 x Schrittmotortreiber
im Bereich 500 Hz - 30 kHz in Abhängigkeit der externen Steuerspannung:

ADC0-Eingang: Richtungssignal an PB0, Schritte an PB1
ADC1-Eingang: Richtungssignal an PB3, Schritte an PB2

Die Impulsdauer beträgt >= 5 µs, die Start- und Endfrequenz kann nach Bedarf angepaßt werden,
wobei die maximale Schrittfrequenz <= 30 kHz sein sollte.

Gemaess den #define Vorgaben ist Rechts-/Linkslauf eingestellt, wobei bei Eingangsspannung
Vin = VCC/2 der Motor angehalten wird und sich die Drehrichtung abhaengig von Vin ergibt:
Vin > VCC/2 oder Vin < VCC/2. 
Die Schrittfrequenz wird proportional zu Vin eingestellt.


http://www.mino-elektronik.de

Alle Angaben ohne Gewaehr !
2016-03-09

*/


#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdlib.h>
#include <math.h>


#define BIT(x)  (1<<x)
#define F_CPU         16000000    // ggf. an eff.Frequenz anpassen

#define BEIDE_RICHTUNGEN          // Rechts-/Linkslauf; Poti-Mitte = Stopp

#define ADC_MAX       1023.0      // bei 10 Bit
#define F_MIN         500         // 500 Hz bei ADC-Eingang = VCC/2
#define F_MAX         30000       // 30 kHz bei ADC-Endanschlag

#define POTI_A        0           // ADC-Eingang 0
#define POTI_B        1           // ADC-Eingang 1


#define STEP_A        1           // an PB.1
#define DIR_A         0           // an PB.0  
#define STEP_B        2           // an PB.2  
#define DIR_B         3           // an PB.3  


#define FILTER        10          // Für langsame Beschleunigungen nach Bedarf erhöhen


volatile uint8_t  neue_werte_fertig_a, richtung_a, sperre_a,
                  neue_werte_fertig_b, richtung_b, sperre_b;
volatile uint32_t neue_periode_a, neue_periode_b; // errechneter Wert für Periodendauer


uint16_t lese_adc(uint8_t kanal)
{
  ADMUX =  BIT(REFS0)| kanal;                   // Vcc als Vref, rechtsbuendig
  ADCSRA |= BIT(ADSC);                          // starten
  while(ADCSRA & BIT(ADSC));                    // warten
  return(ADC);                                  // fertig  
}



int32_t filter_a(int32_t neuer_wert)            // zu schnelle Aenderungen vermeiden
{
#ifdef BEIDE_RICHTUNGEN  
  static int32_t summe = ADC_MAX/2 * FILTER;
#else
  static int32_t summe;
#endif
   summe -= summe/FILTER;
   summe += neuer_wert;
   return summe/FILTER;
}

int32_t filter_b(int32_t neuer_wert)            // zu schnelle Aenderungen vermeiden
{
#ifdef BEIDE_RICHTUNGEN  
  static int32_t summe = ADC_MAX/2 * FILTER;
#else
  static int32_t summe;
#endif
   summe -= summe/FILTER;
   summe += neuer_wert;
   return summe/FILTER;
}

void t1_sync(void)                              // wartet auf Überlauf von Timer1
{
  while(!(TIFR1 & BIT(TOV1)));
  TIFR1 = BIT(TOV1);
}


ISR(TIMER1_COMPA_vect)
{
static uint8_t akt_richtung_a;
static uint32_t nachlade_temp;
static uint32_t periode =  200000;              // default Startwert
uint32_t temp = 0xffff;

  if(nachlade_temp <= temp) temp = nachlade_temp;         
  else if(nachlade_temp < 0x1ffff) temp =nachlade_temp/2; 
  nachlade_temp -= temp;                        // laengere Intervalle stueckweise abarbeiten
  OCR1A += temp;
  TCCR1A &= ~BIT(COM1A0);                       // OC1A loeschen falls gesetzt
  TCCR1C = BIT(FOC1A);                          // per Force Output Compare erzwingen

  if(nachlade_temp) return;                     // Intervall noch nicht abgelaufen
  if(akt_richtung_a) PORTB |= BIT(DIR_A);
  else PORTB &= ~BIT(DIR_A);
  if(!sperre_a) {
    TCCR1A |= BIT(COM1A0);                      // beim nächsten Interrupt OC1B synchron setzen
  }
  nachlade_temp = periode;
  if(neue_werte_fertig_a) {                     // update synchronisieren
    periode = neue_periode_a;
    akt_richtung_a = richtung_a;
    neue_werte_fertig_a = 0;
  }
}

ISR(TIMER1_COMPB_vect)
{
static uint8_t akt_richtung_b;
static uint32_t nachlade_temp;
static uint32_t periode =  200000;              // default Startwert
uint32_t temp = 0xffff;

  if(nachlade_temp <= temp) temp = nachlade_temp;         
  else if(nachlade_temp < 0x1ffff) temp =nachlade_temp/2; 
  nachlade_temp -= temp;                        // laengere Intervalle stueckweise abarbeiten
  OCR1B += temp;
  TCCR1A &= ~BIT(COM1B0);                       // OC1B loeschen falls gesetzt
  TCCR1C = BIT(FOC1B);                          // per Force Output Compare erzwingen

  if(nachlade_temp) return;                     // Intervall noch nicht abgelaufen
  if(akt_richtung_b) PORTB |= BIT(DIR_B);
  else PORTB &= ~BIT(DIR_B);
  if(!sperre_b) {
    TCCR1A |= BIT(COM1B0);                      // beim nächsten Interrupt OC1B synchron setzen
  }
  nachlade_temp = periode;
  if(neue_werte_fertig_b) {                     // update synchronisieren
    periode = neue_periode_b;
    akt_richtung_b = richtung_b;
    neue_werte_fertig_b = 0;
  }
}


void init_timer1_adc()
{
  TCCR1B = 0;                                   // stoppen
  TCNT1  = 0;                             
  TCCR1B = BIT(CS10);                           // freilaufend mit vorteiler /1
  TCCR1A = BIT(COM1A1) | BIT(COM1B1);
  OCR1A  = 0;
  OCR1B  = 0; 
  TIFR1  = BIT(OCF1A) | BIT(OCF1B);             // OCF1x flag loeschen
  TIMSK1 |= BIT(OCIE1A) | BIT(OCIE1B);
  ADCSRA = BIT(ADEN) + 0x07;                    // ADC einschalten mit Teiler /128
  sei();
}


// Hauptprogramm
int main(void)
{
int32_t f_temp;

  CLKPR = 0x80; CLKPR = 0x0;                    // CKDIV auf 0; voller CPU-Takt
  init_timer1_adc();

  DDRB  |= BIT(STEP_A) | BIT(DIR_A) |           // STEP, DIR Ausgang Kanal-A
           BIT(STEP_B) | BIT(DIR_B);            // STEP, DIR Ausgang Kanal-B

  while(1) {
    t1_sync();                                  // Verzoegerung fuer Filter
// Berechnung für Stepper A
    if(!neue_werte_fertig_a) { 
      f_temp = filter_a(lese_adc(POTI_A));      // gemittelter ADC-Wert POTI-A
      f_temp *= F_MAX;
#ifdef BEIDE_RICHTUNGEN
      f_temp -= ADC_MAX/2*F_MAX;                // 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_a = 0;
      if(f_temp >= 0) richtung_a = 1;           // Richtung-Signal abhaengig vom Vorzeichen
      f_temp = abs(f_temp);                     // eff. Ausgangsfrequenz
      if(f_temp < F_MIN) {
        f_temp = F_MIN;                         // nach unten begrenzen
        sperre_a = 1;
      } else sperre_a = 0;
      neue_periode_a = F_CPU/f_temp;            // eff. Periodendauer
      neue_werte_fertig_a = 1;  
    } 

// Berechnung für Stepper B      
    if(!neue_werte_fertig_b) { 
      f_temp = filter_b(lese_adc(POTI_B));      // gemittelter ADC-Wert POTI-B
      f_temp *= F_MAX;
#ifdef BEIDE_RICHTUNGEN
      f_temp -= ADC_MAX*F_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_b = 0;
      if(f_temp >= 0) richtung_b = 1;           // Richtung-Signal abhaengig vom Vorzeichen
      f_temp = abs(f_temp);                     // eff. Ausgangsfrequenz
      if(f_temp < F_MIN) {
        f_temp = F_MIN;                         // nach unten begrenzen
        sperre_b = 1;
      } else sperre_b = 0;
      neue_periode_b = F_CPU/f_temp;            // eff. Periodendauer
      neue_werte_fertig_b = 1;  
    }

  }
}


