/*
Spannungsgesteuerter Taktgeber für Schrittmotore mit ATmega88 und A4982.

Das gezeigte Programm liefert Taktimpulse für einen A4982 Schrittmotortreiber
im Bereich 1kHz - 100kHz in Abhängigkeit der externen Steuerspannung am ADC2-Eingang.
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.

Mit Timer0 wird ein hochfrequentes PWM-Signal an PD.5 ausgegeben und damit der Motorstrom
eingestellt. Im Stillstand wird der Strom auf 1/3 des Fahrstromes gesenkt. Diese Einstellungen
koennen/sollten auf den gewaehlten Schrittmotor angepaßt werden.

Der A4982 wird mit 1/16 Mikroschritt betrieben. Bei einem typischen Motor mit 200 Vollschritten
pro Umdrehung ergibt sich die maximale Drehzahl aus 100000/(200*16) = 31,25 Umdrehungen/s oder
1875 U/min. Schnellere Drehzahlen koennten durch 1/4 Mikroschrittbetrieb eingestellt werden, wobei
Motor, Mechanik und Schrittmotortreiber kaum noch Spielraum für höhere Drehzahlen bieten.

Das Schaltbild zum Programm findet sich hier:
http://www.mino-elektronik.de/bilder/stepper_1x/stepper_1x.png

http://www.mino-elektronik.de

Alle Angaben ohne Gewaehr !
2013-11-06

*/


#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdlib.h>
#include <math.h>


#define BIT(x)  (1<<x)
#define F_CPU             18432000    // ggf. an eff.Frequenz anpassen

#define BEIDE_RICHTUNGEN              // Rechts-/Linkslauf; Poti-Mitte = Stopp

#define ADC_MAX           1023.0      // bei 10 Bit
#define F_MIN             1.0e3       // 1kHz bei ADC-Eingang = VCC/2
#define F_MAX             100.0e3     // 100kHz bei ADC-Endanschlag

#define MAX_STROM         15
#define FAHR_STROM        10
#define RUHE_STROM        FAHR_STROM/3

#define POTI_1            2           // ADC-Eingang 2
#define A4982_MS1         1           // an PB.1
#define A4982_MS2         0           // an PB.0
#define A4982_STEP        2           // STEP an PB.2  
#define A4982_DIR         3           // DIR an PB.3  

#define A4982_SLEEP       4           // an PORTD
#define A4982_PWM         5
#define A4982_ENABLE      6
#define A4982_RESET       7
#define FILTER_LAENGE     16          // nach Bedarf


volatile uint8_t neue_werte_fertig, richtung, inhibit;
volatile uint32_t neue_frequenz;      // errechneter Wert für f


uint16_t  filter_f[FILTER_LAENGE];    // Filter für ADC
uint32_t  filter_summe_f;
uint8_t   filter_index;


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  
}

void adc_eingaenge_filtern(void)
{
  if(filter_index >= FILTER_LAENGE)
    filter_index = 0;
  filter_summe_f -= filter_f[filter_index];
  filter_f[filter_index] = lese_adc(POTI_1);
  filter_summe_f += filter_f[filter_index];
  filter_index++;
}



ISR(TIMER1_COMPB_vect)
{
static uint8_t akt_richtung;
static uint32_t nachlade_temp;
static uint32_t frequenz =  200000;             // default Startwert
uint32_t temp = 0xffff;

  TCCR1A &= ~BIT(COM1B0);                       // OC1B loeschen falls gesetzt
  TCCR1C = BIT(FOC1B);                          // per Force Output Compare erzwingen

  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;
  if(nachlade_temp) return;                     // Intervall noch nicht abgelaufen
  if(akt_richtung) PORTB |= BIT(A4982_DIR);
  else PORTB &= ~BIT(A4982_DIR);
  if(!inhibit) {
    TCCR1A |= BIT(COM1B0);                      // beim nächsten Interrupt OC1B synchron setzen
    OCR0B = FAHR_STROM;
  } else {
    OCR0B = RUHE_STROM;
  }
  nachlade_temp = frequenz;
  if(neue_werte_fertig) {                       // update synchronisieren
    frequenz = neue_frequenz;
    akt_richtung = richtung;
    neue_werte_fertig = 0;
  }
}

void init_timer1_adc()
{
  TCCR1B = 0;                                   // stoppen
  TCNT1  = 0;                             
  TCCR1B = BIT(CS10);                           // freilaufend mit vorteiler /1
  TCCR1A = BIT(COM1B1);
  OCR1B  = 0;                                 
  TIFR1  =  BIT(OCF1B);                         // OCF1B flag loeschen
  TIMSK1 |= BIT(OCIE1B);
  ADCSRA = BIT(ADEN) + 0x07;                    // ADC einschalten mit Teiler /128
  sei();
}

void init_pwm()
{
static char init=0;
  if(init) return;
  init=1;
  OCR0A = MAX_STROM;                            // top-wert
  OCR0B = MAX_STROM/3;                          // 33% PWM default
  TCCR0A |= BIT(COM0B1) + BIT(WGM01) + BIT(WGM00); // fast PWM mit OCR0B
  TCCR0B |= BIT(WGM02) + BIT(CS00);             // top-wert in OCR0A, kein vorteiler
  DDRD  |= BIT(A4982_PWM);                      // PWM-Ausgang
}

// Hauptprogramm
int main(void)
{
uint8_t n;
float f_temp;

  CLKPR = 0x80; CLKPR = 0x0;                    // CKDIV auf 0; voller CPU-Takt
  init_timer1_adc();
  init_pwm();

  DDRB  |= BIT(A4982_MS1) + BIT(A4982_MS2)      // MS1+MS2 Ausgang
         + BIT(A4982_STEP) + BIT(A4982_DIR);    // STEP, DIR Ausgang
  PORTB |= BIT(A4982_MS1) + BIT(A4982_MS2);     // 1/16 Schritt MS1 = 1,  MS2 = 1

  DDRD  |= BIT(A4982_SLEEP)+BIT(A4982_ENABLE)   // Sleep, Enable
         + BIT(A4982_RESET);                    // Reset als Ausgaenge
  PORTD |= BIT(A4982_RESET);                    // Reset wegnehmen
  PORTD |= BIT(A4982_SLEEP);                    // sleep freigeben
  PORTD &= ~BIT(A4982_ENABLE);                  // enable Ausgaenge
  for(n = 0; n > FILTER_LAENGE; n++)
    adc_eingaenge_filtern();
  while(1) {
    adc_eingaenge_filtern();
    if(!neue_werte_fertig) { 
      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
        inhibit = 1;
      } else inhibit = 0;
      f_temp = F_CPU/f_temp;                    // eff. Periodendauer
      neue_frequenz = (f_temp + 0.5);           // ggf. aufgerundet
      neue_werte_fertig = 1;  
    }    
  }
}


