/*
Programm für ATtiny4313

Am Ausgang OC1B wird ein Impuls ausgegeben, der mit der Variablen 'W' in µs
eingestellt wird.
Die Periodendauer (1/Frequenz) wird mit 'P' in µs eingestellt.
Die Grenzwerte werden durch die entsprechenden #define-Anweisungen vorgegeben.

Die Minima per #define sind 5µs für die Pulsweite und 20 µs (50kHz) für die 
Periodendauer, die Maxima 10s und 100s für die Periodendauer (0,01Hz).
Ferner lässt sich die Impulserzeugung per Befehl 'O' ein-/ausschalten.

Als Default-Wert nach dem Einschalten sind 50Hz mit 50% Tastverhältnis vorgegeben.

Die Befehle werden durch das Steuerzeichen ESC oder einen '.' eingeleitet,
welche per ser. Schnittstelle (mit DEF_BAUDRATE) empfangen werden:

.1000W  stellt die Pulsweite auf 1000µs => 1ms
.2000P  stellt die Periodendauer auf 2ms, was 200Hz entspricht
.1O     schaltet die Impulserzeugung ein
.0O     schaltet sie aus.

Um zum Beispiel jede Sekunden einen 5µs Impuls auszugeben muss man eingeben:
.5W
.1000000P

Die Werte können auch ausgelesen werden, indem der Befehl ohne Paramter 
angegeben wird:

.W    liefert als Antwort die Pulsweite in der Form:  W1000<CR><LF>
.P    den eingestellten Werte der Periodendauer:      P2000<CR><LF>
.O    die Zeichenfolge O0<CR><LF> oder O1<CR><LF> wie zuvor eingestellt
.V    gibt die Version aus

http://www.mino-elektronik.de

Alle Angaben ohne Gewaehr !
2013-10-23


2013-10-30 Programmerweiterung:
Die zuletzt eingestellten Werte werden gespeichert und beim naechsten Einschalten verwendet.
Ferner sind neue Befehle hinzugekommen:

.nnnnF  eine Frequenz in Hz kann direkt eingeben werden. Aufloesung in Timertakten wird errechnet.
.nnZ    zusaetzliche Timertakte werden zu 'P' oder 'W' fuer feinere Aufloesung angegeben.
        Zuerst muss 'Z' eingegebn werden und danach der eigentliche Wert in Mikrosekunden
.Z      zeigt die Zusatztakte zur letzten Abfrage von 'P', 'W' oder 'F' an.
.A      Die Anzahl der Timertakte/Mikrosenkunde wird ausgegeben. typ. 20 bei 20MHz

*/

#define   VERSION "mino FG_4313 V1.1 2013-10-30"

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <stdlib.h>
#include <ctype.h>
#include <avr/eeprom.h>

// zur Speicherung im EEPROM
#define EEAR_SIZE   uint8_t                     // Tiny4313 hat nur 256 Byte
#define EE_OO_ADR   1                           // 1 = eingeschaltet
#define EE_P_ADR    2                           // als uint32_t
#define EE_W_ADR    6                           // dto.


#define BIT(x)  (1<<x)
#define F_CPU           20000000                // 20MHz ggf. an eff.Frequenz anpassen
#define US_FAKTOR       (F_CPU/1000000L)        // Anzahl Takte fuer 1µs
#define DEF_BAUDRATE    19200

#define DEF_PERIODE     20000                   // 50Hz
#define DEF_PULSWEITE   (DEF_PERIODE/2)         // 50%-Tastverhaeltnis

#define PERIODE_MIN     20                      // 20µs = 50kHz
#define PERIODE_MAX     100000000               // 0,01Hz
#define PULSWEITE_MIN   5                       // 5µs
#define PULSWEITE_MAX   10000000                // 10s 
#define PW_RESERVE      10                      // 10µs Pulsweite-max immer kleiner als Periodendauer-PW_RESERVE

#define PW_AUSGANG      4                       // PW-Signal an OC1B (PB.4)

#define ESC_BIT         1
#define ESC             27
#define PULSWEITE       'W'                     // Werte in Mikrosekunden
#define PERIODE         'P'                     // dto.
#define FREQUENZ        'F'                     // Wert in Hz
#define ZUSATZ_TAKTE    'Z'                     // Anzahl der zusaetzlichen Timertakte
#define AUFLOESUNG      'A'                     // Aufloesung des Timers: Takte / Mikrosekunde
#define EIN_AUS         'O'                     // 1 oder 0
#define SENDE_VERSION   'V'


uint8_t  ser_status;

#define RXD_PUFFER_LEN  10
#define TXD_PUFFER_LEN  10
#define LF              10
#define CR              13

static uint8_t    txd_puffer[TXD_PUFFER_LEN+1], rxd_puffer[RXD_PUFFER_LEN+1];
volatile uint8_t  rxd_puffer_lese_zeiger, rxd_puffer_schreib_zeiger,
                  txd_puffer_lese_zeiger, txd_puffer_schreib_zeiger;
volatile uint8_t  rxd_puffer_anzahl, txd_puffer_anzahl;

volatile uint8_t  ein_aus_flag;               
uint8_t  neue_werte_bereit, neuer_wert;                       // Sync-Flags
uint32_t neue_periode, periode = DEF_PERIODE*US_FAKTOR;       // Wert für Periode
uint32_t neue_pulsweite, pulsweite = DEF_PULSWEITE*US_FAKTOR; // Wert für Pulsweite
uint32_t zusatz_takte;                                        // timer-Takte fuer feinere Aufloesung
int32_t zahl=0;

// nicht ins RAM kopieren
const uint32_t tab[] PROGMEM = {1000000000, 100000000, 10000000, 1000000,
                    100000, 10000, 1000, 100, 10, 1, 0};


ISR (USART_RX_vect)
{
uint8_t temp;
  UCSRB &= ~BIT(RXCIE);                             // rx zunaechst sperren 
  sei();                                            // Ints zulassen
  temp = UDR;                                       // zeichen immer lesen
  if(rxd_puffer_anzahl < RXD_PUFFER_LEN) {
    rxd_puffer[rxd_puffer_schreib_zeiger++] = temp;
    if(rxd_puffer_schreib_zeiger >= RXD_PUFFER_LEN) rxd_puffer_schreib_zeiger = 0;
    rxd_puffer_anzahl++;
  }
  UCSRB |= BIT(RXCIE);                              // rx wieder freigeben
}

ISR (USART_UDRE_vect)
{
  UCSRB &= ~BIT(UDRIE);                             // sender zunaechst sperren
  sei();                                            // Ints zulassen
  if(txd_puffer_anzahl) {                           // noch zeichen im puffer
    UDR = txd_puffer[txd_puffer_lese_zeiger];       // Zeichen senden: ins register
    if(++txd_puffer_lese_zeiger >= TXD_PUFFER_LEN) txd_puffer_lese_zeiger = 0;
    txd_puffer_anzahl--;
    UCSRB |= BIT(UDRIE);                            // sender wieder freigeben
  }
}

void set_baudrate1(uint32_t baudrate)
{
  UBRRL=((F_CPU/16/(baudrate/10)+5)/10) - 1;        // runden !!! 
  UBRRH=0;
}

void clr_txd_puffer(void)
{
  UCSRB &= ~BIT(UDRIE);                             // int sofort sperren 
  txd_puffer_lese_zeiger = 0;
  txd_puffer_schreib_zeiger = 0;
  txd_puffer_anzahl = 0;
}

void clr_rxd_puffer(void)
{
  UCSRB &= ~BIT(RXCIE);                             // nicht unterbrechen 
  rxd_puffer_anzahl = 0;
  rxd_puffer_lese_zeiger = 0;
  rxd_puffer_schreib_zeiger = 0;
  UCSRB |= BIT(RXCIE);                              // rx immer aktiv 
}

void init_serio1(uint32_t baudrate)
{
uint8_t dummy_temp;  
  UCSRB = 0x0;                                      // TX und RX sperren 
  clr_rxd_puffer();
  clr_txd_puffer();
  set_baudrate1(baudrate);
  UCSRB |= BIT(RXEN);
  UCSRB |= BIT(TXEN);
  dummy_temp = UDR;                                 // evtl. Zeichen lschen 
  UCSRB |= BIT(RXCIE);                              // rx immer aktiv 
  sei();
}

void ser_ausgabe(uint8_t c)
{
  while(txd_puffer_anzahl >= TXD_PUFFER_LEN);       // warten, wenn puffer voll ist 
  txd_puffer[txd_puffer_schreib_zeiger++] = c;      // zeichen in puffer geben 
  if(txd_puffer_schreib_zeiger >= TXD_PUFFER_LEN) txd_puffer_schreib_zeiger = 0;
  UCSRB &= ~BIT(UDRIE);                             // nicht unterbrechen 
  txd_puffer_anzahl++;
  UCSRB |= BIT(UDRIE);                              // ausgabe starten, wenn zulssig 
}

uint8_t lese_rxd_puffer(void)
{
uint8_t c;
  if(rxd_puffer_anzahl == 0) return(0);
  c = rxd_puffer[rxd_puffer_lese_zeiger++];
  if(rxd_puffer_lese_zeiger >= RXD_PUFFER_LEN) rxd_puffer_lese_zeiger = 0;
  UCSRB &= ~BIT(RXCIE);                             // nicht unterbrechen 
  rxd_puffer_anzahl--;
  UCSRB |= BIT(RXCIE);
  return(c);
}

uint8_t lese_rxd_puffer_anzahl(void)
{return(rxd_puffer_anzahl);}

uint8_t lese_txd_puffer_anzahl(void)
{return(txd_puffer_anzahl);}

void sende_cr_lf(void)
{
  ser_ausgabe(10);
  ser_ausgabe(13);
}

void sende_str(char * s)
{
  while(*s) ser_ausgabe(*s++);
  sende_cr_lf();
}

void sende_version(void)
{
  sende_str(VERSION);
}

// wandelt int32_t nach String; mit0 = 1 zeigt fuehrende '0'en an.
void long2a(int32_t l, char *s, uint8_t mit0)
{
uint32_t temp;
uint8_t i=0,j;
  if(l || mit0) {
    if(l < 0) {
      l = -l;
      *s++ = '-';
    }
    if(mit0) {
      i=2;
      while((uint32_t) pgm_read_dword(&tab[i]) > l) {
          i++;
          *s++ = '0';
      }
    } else
      while((uint32_t) pgm_read_dword(&tab[i]) > l) i++;
      do {
        temp = (uint32_t) pgm_read_dword(&tab[i]);
        j = '0';
        if(temp) {
            for(;j <= '9'; j++) {
              l-=temp;
              if(l < 0) {
                l += temp;
                break;
              }
            }
            *s++ = j;
            i++;
        }
      } while(temp);
    } else *s++ = '0';
  *s = 0;
}

// Ausgabe eines int32_t Wertes linksbuendig ohne fuehrende '0'
void sende_integer(int32_t l)
{
char s[15];
  long2a(l,s,0); 
  sende_str(s);
}

// integer Zahlen einlesen und Befehl auswerten
void bewerte_ser_input(void)
{
static uint8_t ziffern;
uint8_t temp;
  if(!lese_rxd_puffer_anzahl()) return;               // kein zeichen
  temp = lese_rxd_puffer();                           // sonst lesen
  if(temp == ESC || temp == '.') {                    // Datensatz beginnt mit ESC 
    ser_status = ESC_BIT;
    ziffern = 0;
    zahl = 0;
    return;
  }
  if((ser_status & ESC_BIT) == 0) {                   // kein ESC_bit
    ser_status = 0;                                   // dann alles auf Anfang
    ziffern = 0;
    zahl=0;                                           // vermeintlich doppelte Zuweisungen
    return;                                           // werden vom Compiler optimiert
  }
 
  if(temp>= '0' && temp<= '9') {                      // Zahlenwert einlesen
    ziffern++;
    zahl = zahl * 10L + (temp & 0xf);                 // x 10 + neue Ziffer 
    return;
  }

  // ab hier nur noch befehle 

  temp = toupper(temp);
  switch(temp) {
    case PULSWEITE:
      if(ziffern) {                                   // neue Pulsweite schreiben
        if(zahl < PULSWEITE_MIN) zahl = PULSWEITE_MIN;
        if(zahl > PULSWEITE_MAX) zahl = PULSWEITE_MAX;
        pulsweite = zahl * US_FAKTOR + zusatz_takte;
        neuer_wert = 1;
      } else {                                        // andernfalls Wert ausgeben
        ser_ausgabe(temp);
        sende_integer(pulsweite / US_FAKTOR);
        zusatz_takte = pulsweite % US_FAKTOR;
      }
    break;
  
    case PERIODE:                                     // Periodendauer schreiben/anzeigen
      if(ziffern) {
        if(zahl < PERIODE_MIN) zahl = PERIODE_MIN;
        if(zahl > PERIODE_MAX) zahl = PERIODE_MAX;
        periode = zahl * US_FAKTOR + zusatz_takte;
        neuer_wert = 1;
      } else {
        ser_ausgabe(temp);
        sende_integer(periode / US_FAKTOR);
        zusatz_takte = periode % US_FAKTOR;
      }
    break;
    case FREQUENZ:                                    // Frequenz schreiben/anzeigen
      if(ziffern) {
        if(zahl != 0) {
          periode = F_CPU / zahl;                     // auf timer-Takte umrechnen
          if(periode < PERIODE_MIN*US_FAKTOR) zahl = PERIODE_MIN*US_FAKTOR;
          if(periode > PERIODE_MAX*US_FAKTOR) periode = PERIODE_MAX*US_FAKTOR;
          neuer_wert = 1;
        }
      } else {
        ser_ausgabe(temp);
        sende_integer(F_CPU / periode);
      }
    break;
    case ZUSATZ_TAKTE:                                // feinere Aufloesung in Timertakten
      if(ziffern) {
        zusatz_takte = zahl;                          // Wert erst mit naechstem Befehl schreiben
        neuer_wert = 1;
      } else {
        ser_ausgabe(temp);
        sende_integer(zusatz_takte);                  // oder Wert zum letzten Lesebefehl anzeigen
      }
    break;
    case EIN_AUS:                                     // Impulserzeugung ein/ausschalten
      if(ziffern) {
        if(zahl) ein_aus_flag = 1;
        else     ein_aus_flag = 0;
        neuer_wert = 1;                               // neuen Status abspeichern
      } else {
        ser_ausgabe(temp);
        sende_integer(ein_aus_flag);                  // Status anzeigen
      }
    break;
    case AUFLOESUNG:                                  // Timertakte pro Mikrosekunde 
      ser_ausgabe(temp);
      sende_integer(US_FAKTOR);                       // zur Berechnung von Zusatztakten
      
    break;
    case SENDE_VERSION:                               // Versionsanzeige
      sende_version();
    break;
  } //switch
  ser_status = 0;                                     // auf neuen Befehl warten
  ziffern = 0;
  zahl=0;
}



ISR(TIMER1_COMPB_vect)
{
static uint32_t nachlade_temp, pw_nachlade_temp;
static uint8_t trigger_pw = 1, init;
static uint32_t periode  =  DEF_PERIODE * US_FAKTOR;    // default Startwerte @ 20MHz
static uint32_t pulsweite = DEF_PULSWEITE * US_FAKTOR;  // 50Hz und 10ms Pulsweite 
uint32_t temp = 0xffff;

  if(trigger_pw) {
    if(pw_nachlade_temp <= temp) temp = pw_nachlade_temp;         
    else if(pw_nachlade_temp < 0x1ffff) temp = pw_nachlade_temp/2; 
    pw_nachlade_temp -= temp;                           // laengere PW-Intervalle stueckweise abarbeiten
    OCR1B += temp;
    if(pw_nachlade_temp == 0) {                         // Pulsweite macht den letzten Zyklus
      TCCR1A &= ~BIT(COM1B0);                           // danach OC1B synchron loeschen wenn TCNT1 == OCR1B
      nachlade_temp = periode - pulsweite;              // Restwert für Periodendauer verwenden
      trigger_pw = 0;
    }
    return;                                             // Pulsweite ist noch aktiv
  }

  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
  trigger_pw = 1;                                       // Ausgangsimpuls im nächsten Zyklus ausgeben
  if(ein_aus_flag && init)
    TCCR1A |= BIT(COM1B0);                              // beim nächsten Interrupt OC1B synchron setzen
  pw_nachlade_temp = pulsweite;
  if(neue_werte_bereit) {                               // update synchronisieren
    periode = neue_periode;
    pulsweite = neue_pulsweite;
    init = 1;                                           // 1. Impuls unterdruecken bis neue Werte vorhanden
    neue_werte_bereit = 0;
  }
}

uint8_t ee_read_byte(EEAR_SIZE adr)
{
  while(EECR & BIT(EEPE));          // abwarten
  EEAR = adr;
  EECR |= BIT(EERE);                // lesebit setzen
  return(EEDR);
}

void ee_write_byte(uint8_t data, EEAR_SIZE adr)
{
  if(data != ee_read_byte(adr)) {   // nur abweichende Werte schreiben
    cli();
    EEAR = adr;
    EEDR = data;
    EECR  = BIT(EEMPE);             // master schreibbit setzen, EEPMx löschen
    EECR |= BIT(EEPE);              // schreibbit setzen
    sei();
  }
}

void ee_write_n(EEAR_SIZE adr, uint8_t *p, EEAR_SIZE anzahl)
{
  while(anzahl--) {
    ee_write_byte(*p++, adr++);
  }
}

void ee_read_n(EEAR_SIZE adr, uint8_t *p, EEAR_SIZE anzahl)
{
  while(anzahl--) {
      *p++ = ee_read_byte(adr++);
  }
}

// bei Bedarf aktivieren
/*void ee_write_word(uint16_t data, EEAR_SIZE adr)
{
  ee_write_n(adr, (uint8_t *)&data, sizeof(uint16_t));
}

uint16_t ee_read_word(EEAR_SIZE adr)
{
uint16_t temp;
  ee_read_n(adr, (uint8_t *)&temp, sizeof(uint16_t));
  return(temp);
}
*/

void ee_write_long(uint32_t data, EEAR_SIZE adr)
{
  ee_write_n(adr, (uint8_t *)&data, sizeof(uint32_t));
}

uint32_t ee_read_long(EEAR_SIZE adr)
{
uint32_t temp;
  ee_read_n(adr, (uint8_t *)&temp, sizeof(uint32_t));
  return(temp);
}


void init_timer1()
{
  TCCR1B = 0;                                   // stoppen
  TCNT1  = 0;                             
  TCCR1A = BIT(COM1B1);                         // OC1B auf 0 lassen
  TCCR1B = BIT(CS10);                           // freilaufend mit vorteiler /1
  OCR1B  = 0;                                 
  TIFR  =  BIT(OCF1B);                          // OCF1B flag loeschen
  TIMSK |= BIT(OCIE1B);
  sei();
}



// Hauptprogramm
int main(void)
{
  CLKPR = 0x80; CLKPR = 0x0;                              // CKDIV auf 0; voller CPU-Takt
  DDRB = BIT(PW_AUSGANG);                                 // PB.4 als Ausgang
  ein_aus_flag = ee_read_byte(EE_OO_ADR);                 // EEPROM schon initialisiert?
  if(ein_aus_flag <= 1) {                                 // ein_aus_flag hat gueltigen Wert
    neue_periode = periode = ee_read_long(EE_P_ADR);      // dann gespeicherte Werte holen
    neue_pulsweite = pulsweite = ee_read_long(EE_W_ADR);
    neue_werte_bereit = 1;                                // und mit 1. Interrupt uebernehmen
  } else {                                                // sonst mit Defaultwerten starten
    ein_aus_flag = 1; 
    neuer_wert = 1;                                       // und abspeichern
  }
  init_timer1();
  init_serio1(DEF_BAUDRATE);
  sende_version();

  while(1) {
    bewerte_ser_input();
    if(!neue_werte_bereit && neuer_wert) {                // neue Werte koennen geschrieben werden
      if(pulsweite + (PW_RESERVE * US_FAKTOR) > periode)
        pulsweite = periode - (PW_RESERVE * US_FAKTOR);
      neue_pulsweite = pulsweite;
      neue_periode = periode;
      ee_write_byte(ein_aus_flag, EE_OO_ADR);             // aktuellen Zustand speichern
      ee_write_long(periode, EE_P_ADR);
      ee_write_long(pulsweite, EE_W_ADR);
      neuer_wert = 0;
      neue_werte_bereit = 1;                              // und per Interrupt uebernehmen
    }
  }
}


