/*
Bespielprogramm für eine kleine Platine mit ATmega328 und A4982 als Schrittmotortreiber.

Das Programm dient zum Anfahren einer absoluten Position, wobei Beschleunigungs- und Bremsrampen
automatisch erzeugt werden. Die Naeherungsfunktion fuer die Rampen ist recht schnell, sodass die 
Laufgeschwindigkeit auch während der Fahrt verändert werden kann.
Das Beispiel TONLEITER macht dies deutlich.

Wie in der Routine init_stepper() zu sehen, wird der A4982 mit 1/16 Schritt initialisiert.
Mit den Variablen 'schnell' und 'langsam' werden die Schrittfrequenzen für Fahrten auf die
angewählte Endposition festgelegt. Abhängig vom verwendeten Motor und der zu bewegenden Mechanik
sind die Werte 'steigung', 'ruhestrom' und 'fahrtstrom' einzustellen.
Kleiner Tipp: die Stroeme nur so hoch einstellen wie notwendig; das ergibt die beste Laufruhe!


Nach der Initialisierung sind folgende Funktionen nutzbar:

void stepper_soll_position(int32_t x)             faehrt den Motor auf die abs. Soll-Position
int32_t lese_ist_position(void)                   liest die aktuelle Position
void stepper_geschwindigkeit(int32_t neuer_wert)  Vorgabe neue Endgeschwindigkeit auch waehrend der Fahrt
int8_t stepper_aktiv(void)                        liefert 1, solange der Motor noch in Bewegung ist
void stepper_abwarten(void)                       wartet das Ende der Fahrt ab
void stepper_abs_position(int32_t x)              setzt aktuelle Position auf Vorgabewert 'x'
void stoppe_stepper(void)                         stoppt den Motor ohne Schrittverlust
void stepper_relativ(int32t_t offset)             relative Bewegung zur akt. Position
void stepper_plus(void)                           ein Schritt vor
void stepper_minus(void)                          ein Schritt zurueck

Schaltbild und Leiterplatte zum Programm finden sich hier:
http://www.mino-elektronik.de/bilder/M8_step8/M8_step8_sch.png
http://www.mino-elektronik.de/bilder/M8_step8/M8_step8_brd.png

http://www.mino-elektronik.de

Alle Angaben ohne Gewaehr !
2014-09-18
*/

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdlib.h>

#define TONLEITER
#define DEF_CPU_CLOCK 18432000

#define BIT(x)  (1<<x)

#define INT_PRO_SEKUNDE   1000        // für Warteschleifen

#define MAX_STROM         15
#define FAHR_STROM        10
#define RUHE_STROM        FAHR_STROM/3

#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

static volatile uint8_t sync_flag;

#define TIMER1_FREQUENZ   DEF_CPU_CLOCK

#define STEPPER_INT_AUS {if(TIMSK1 & BIT(OCIE1B)) TIMSK1 &= ~BIT(OCIE1B);\
                         else TIFR1 = BIT(OCIE1B);}   // t1 int abschalten
#define STEPPER_INT_EIN (TIMSK1 |= BIT(OCIE1B))       // t1 int einschalten

uint32_t  langsam = 3200,                             // 200 Vollschritte/s = 60 UPM bei 1,8° Motor
          schnell = 32000;                            // max. 50000 Schritte/s @20MHz ist so die Grenze
uint16_t  steigung = 50,                              // zuegige Beschleunigung
          temp_steigung;
uint8_t   i_stop, i_run,
          ruhestrom = RUHE_STROM, 
          fahrtstrom = FAHR_STROM; 
int32_t   soll_position;                              // anzufahrende Position
volatile int32_t  ist_position;                       // aktuelle Position
volatile int8_t schritte_aktiv, stepper_ack;          // flag, ob Stepper noch aktiv
int16_t   i_tout;                                     // nach Ablauf auf Ruhestrom schalten
uint32_t  stepper_langsam, stepper_schnell,           // zeiten in 0,054us !!!
          nachladewert;                               // akt. Zeit zu Schrittfrequenz
int16_t   brems_schritte;



void schreibe_strom(uint8_t wert)
{
  if(wert > MAX_STROM) wert = MAX_STROM;              // auf 100% begrenzen
  OCR0B = wert;
}


ISR(TIMER1_COMPB_vect)
{
static uint16_t tout, inc_dec;
static uint32_t nachlade_temp=0;
uint32_t temp = 0xffff;
int32_t diff_wert;
  TCCR1A &= ~BIT(COM1B0);                             // OC1B loeschen falls gesetzt
  TCCR1C = BIT(FOC1B);                                // per Force Output Compare erzwingen

  if(!nachlade_temp) {
    nachlade_temp = nachladewert;                     // neu uebernehmen


  }
  if(nachlade_temp <= temp) temp = nachlade_temp;     // int16_t wert: typisch
  else if(nachlade_temp < 0x1ffff)                    // beim Restwert nur
         temp =nachlade_temp/2;                       // den halben Wert nehmen
  nachlade_temp-=temp;                                // reduzieren bis 0
  OCR1B += temp;                                      // fuer naechsten Interrupt
  if(nachlade_temp) return;                           // noch nicht fertig, ISR abbrechen


  stepper_ack=0;                                      // Schritt ausgefuehrt, flag loeschen
  diff_wert=soll_position-ist_position;               // differenz bilden

  if(!diff_wert) {
    schritte_aktiv = 0;                               // fahrt ist zuende
    if(tout) {
      tout--;
      nachladewert = stepper_langsam;
      brems_schritte = 0;
    } else {
      schreibe_strom(i_stop);                         // Ruhestrom einschalten
      STEPPER_INT_AUS;                                // interrupts abschalten
    }
    return;                                           // ISR abbrechen
  }

  if(diff_wert < 0) {
    PORTB &= ~BIT(A4982_DIR);
    ist_position--;
    diff_wert = -diff_wert;                           // abs-wert wird benoetigt
  } else {
    PORTB |= BIT(A4982_DIR);
    ist_position++;
  }
  TCCR1A |= BIT(COM1B0);                              // beim nächsten Interrupt OC1B synchron setzen

  if(!schritte_aktiv) {
    schritte_aktiv=1;
    tout = i_tout;
    schreibe_strom(i_run);                            // hohen Strom einschalten
  }

  if(nachladewert != stepper_schnell) {
    temp = nachladewert;
    inc_dec = temp_steigung;

//  Schrittweite (Rampe) berechnen
    while(temp < TIMER1_FREQUENZ/1000) {
      temp <<= 1;
      inc_dec >>= 1;
    }
  }

  if(!inc_dec) inc_dec=1;                             // minimal 1
  if(diff_wert > brems_schritte) {
    if(nachladewert == stepper_schnell) return;       // ISR abbrechen
    if(abs(nachladewert-stepper_schnell) <= inc_dec) nachladewert = stepper_schnell;
    else if(nachladewert > stepper_schnell) {
      nachladewert -= inc_dec;
      brems_schritte++;
  }
    else if(nachladewert < stepper_schnell) {
      nachladewert += inc_dec;
      brems_schritte--;
    }
  } else {
    if(nachladewert < stepper_langsam) nachladewert += inc_dec;
    brems_schritte--;
  }
}

void init_timer1(void)
{
  TCCR1B = 0;                                         // stoppen
  TCNT1  = 0;
  TCCR1B = BIT(CS10);                                 // freilaufend mit vorteiler /1
  TCCR1A = BIT(COM1B1);                               // set-reset an OC1B
  OCR1A = TIMER1_FREQUENZ/1000;                       // 1ms Interrupts 
  TIMSK1 |= BIT(OCIE1A);

  OCR1B = stepper_langsam;
  TIFR1  =  BIT(OCF1B);                               // OCF1A flag loeschen
  STEPPER_INT_EIN;                                    // int freigeben
  sei();
}

void stepper_geschwindigkeit(int32_t neuer_wert)
{
  STEPPER_INT_AUS;
  stepper_schnell = TIMER1_FREQUENZ/neuer_wert;       // neue Geschwindigkeit 
  STEPPER_INT_EIN;                                    // int freigeben
}

void stepper_parameter(int8_t i_low, int8_t i_high)
{
  i_stop = i_low;
  i_run = i_high;
}


void init_stepper(uint32_t langsam, uint32_t schnell, uint16_t stg)
{
static int8_t init=0;
  if(langsam > schnell) langsam = schnell;
  i_tout = langsam/10;                                // 0.1 s

  nachladewert = stepper_langsam = TIMER1_FREQUENZ/langsam;
  stepper_schnell = TIMER1_FREQUENZ/schnell;
  temp_steigung = stg;
  if(!init) {
    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
    init_timer1();
  }
}

int8_t stepper_aktiv(void)
{
  return(stepper_ack | schritte_aktiv);               // 1 = Stepper noch aktiv           
}

void stepper_abwarten(void)
{ 
  while(stepper_aktiv());                             // solange Stepper nicht fertig
}

int32_t lese_ist_position(void)
{
int32_t temp;
  STEPPER_INT_AUS;    // int sperren
  temp = ist_position;
  STEPPER_INT_EIN;    // int freigeben
  return(temp);
}

void stepper_soll_position(int32_t x)
{
  STEPPER_INT_AUS;    // int sperren
  soll_position = x;
  stepper_ack = 1;
  STEPPER_INT_EIN;    // int freigeben
}

void stepper_abs_position(int32_t x)
{
  STEPPER_INT_AUS;    // int sperren
  soll_position = ist_position = x;
  stepper_ack = 1;
  STEPPER_INT_EIN;    // int freigeben
}

void stoppe_stepper(void)
{
int32_t rest_schritte;
  STEPPER_INT_AUS;    // int sperren
  rest_schritte = brems_schritte;
  if(soll_position < ist_position) 
    rest_schritte = -rest_schritte;                   // andere richtung
  stepper_soll_position(ist_position+rest_schritte);  // abbremsen
  STEPPER_INT_EIN;    // int freigeben
}

void stepper_relativ(int32_t offset)
{
  STEPPER_INT_AUS;    // int sperren
  soll_position += offset;
  stepper_ack = 1;
  STEPPER_INT_EIN;    // int freigeben
}

void stepper_plus(void)
{
  stepper_relativ(1);
}

void stepper_minus(void)
{
  stepper_relativ(-1);
}

ISR (TIMER1_COMPA_vect)
{
  OCR1A += TIMER1_FREQUENZ/1000;                      // 1ms Interrupts 
  sync_flag=1;
}

void init_pwm(void)
{
static int8_t 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
}


static void sync(void)
{
  sync_flag=0;
  while(!sync_flag);
}

void warte_ms(int16_t zeit)
{
  while(zeit--) sync();
}


int main(void)
{
#ifdef TONLEITER
int16_t i;
int32_t tonleiter[] = {10000, 11225, 12600, 13348, 14983, 16817, 18877, 20000,  // hoch
                       20000, 18877, 16817, 14983, 13348, 12600, 11225, 10000,  // runter
                       20000, 10000, 5000, 10000, 12600, 14983, 20000}; // Tusch
  CLKPR = 0x80; CLKPR = 0x0;                        // CKDIV auf 0; voller CPU-Takt
#endif
  init_pwm();                                       // fuer Motorstrom
  init_stepper(langsam, schnell, steigung);         // Vorgabewerte einstellen
  stepper_parameter(ruhestrom, fahrtstrom);         // Stromwerte
  warte_ms(200);
#ifdef TONLEITER                                    // als Spass und Nervensaege
  while(1) {
    stepper_geschwindigkeit(tonleiter[0]);
    stepper_soll_position(10000000);                // Dauerlauf erzwingen
    warte_ms(300);
    for(i=0; i < sizeof(tonleiter)/sizeof(*tonleiter); i++) {
      stepper_geschwindigkeit(tonleiter[i]);        // Melodie spielen
      warte_ms(300);
    }
    warte_ms(500);
    stoppe_stepper();
    stepper_abwarten();                             // Stepper stoppen
    stepper_geschwindigkeit(32000);                 // und recht schnell
    stepper_soll_position(0);                       // auf Anfangsposition zurueck
    stepper_abwarten();
    warte_ms(1000);                                 // kleine Pause fuer die Ohren
  }
#else
 do {                                               // sonst kleiner Funktionstest
   stepper_soll_position(64000);                    // auf Position 64000 fahren
   stepper_abwarten();
   warte_ms(500);
   stepper_soll_position(0);                        // wieder zurueck
   stepper_abwarten();
   warte_ms(500);
 } while(1);                                        // bis auf Weiteres
#endif
}
