/* STM32F407
Beispielprogramm für Frequenz- und Pulsweitenmessung an PC6 mit Timer 8.
Das Ergebnis wird 6-stellig mit typ. 3 Mess./s oder minimal 1 Periodendauer auf
einem LCD-Modul in der 1. Zeile angezeigt.
Der Frequenzbereich ist 0,05 Hz - <= 500 kHz.

Das Messverfahren wird unter http://www.mino-elektronik.de/fmeter/fmeter.htm#funk
näher beschrieben. Dort finden sich Beispielprogramme und weitere Schaltungen
für AVR-Controller.

Neben der Frequenzmessung wird noch die Pulsweite des pos. Signalanteils gemessen.
Daraus werden das Tastverhaeltnis (in der 2. LCD-Teile) und +Pulsweite und -Pulsweite
(3. und 4. LCD-Zeile) ermittelt und angezeigt.
Fuer die Anzeige von Frequenz und Tastverhaeltnis reicht ein zweizeiliges Display 2x16.
Erst bei Anzeigen 4x16 oder 4x20 werden die Pulsweiten mit angezeigt.
Weiter unten im Programm kann das nach Bedarf angepasst werden.

Das vorliegende Programm wurde mit Em::Blocks 2.2 entwickelt.
Der Prozessor muß mit HCLK = 168MHz initialisiert sein.

Die Anschlussbelegung der Ports ist so gewaehlt, dass das Programm unveraendert
auf einen STM32F407-Discovery-Board funktionsfaehig ist!

Alle Angaben ohne Gewähr!

www.mino-elektronik.de
2012-11-26
*/


#include <stdio.h>
#include "stdint.h"
#include "stm32f4xx.h"

#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"

// LCD-Routinen in anderem Programmteil
extern void lcd_zeile1(char *s);
extern void lcd_zeile2(char *s);
extern void lcd_zeile3(char *s);
extern void lcd_zeile4(char *s);
void lcd_init(void);

enum status {MESSEN=0, AUSLESEN, AUSWERTEN};    // die Phasen der Messung

#define F_CLOCK 168e6                           // zum genauen Abgleich bitte anpassen!
#define EINE_SEKUNDE        (F_CLOCK / 65536)
#define ABBRUCH             (EINE_SEKUNDE*20)   // minimal 0,05Hz Eingangsfrequenz

#define MESSZEIT            (EINE_SEKUNDE/3)    // ca. 3 Messungen/sek.
#define STELLEN 6                               // Aufloesung
#define RUNDUNG 5e-6                            // Wert zum Runden


volatile double     ref_frequenz = F_CLOCK;     // grundfrequenz
double              frequenz;                   // aktueller Messwert

char f_dim[][4] =  {"GHz","MHz","kHz","Hz ","mHz",  // Dimensionen fuer Frequenzen
                    "sek","ms ","us ","ns ","ps "}; // Dimensionen fuer Zeiten

volatile uint8_t    messwert_vorhanden,         // F1 nur gueltige Messungen anzeigen
                    mess_status;                // Ablaufsteuerung

volatile uint16_t   mess_dauer,                 // minimale Wartezeit
                    ueberlauf;                  // fuer Zeitmessung


volatile uint32_t   mess_zeit_1,                // gesamtes Intervall
                    mess_zeit_2,                // pos. Anteil des Intervalls
                    mess_ereignisse;            // Anzahl der Eingangsimpulse


// Routine zur Wandlung und Ausgabe eines Meßwertes
void zeige_x(char *header, double x, char zeige_periode, char *s)    // Anzeige von Frequenz oder Periode
{
int8_t i,j,dez_punkt,dimension;
char *p;

  dez_punkt = 0;
  dimension = 3;
      p = header;
      while(*p) *s++ = *p++;                // "P = " für Periodendauer
  if(x >= 0.001) {                          // 1mHz ist Untergrenze
    if(zeige_periode) {
      x = 1/x;                              // Kehrwert bilden
      dimension=5;                          // und in Sekunden als Dimension
    }
    while(x<1.0) {x*=1000.0;dimension++;}   // in den Hz-Bereich bringen
    while(x>=1000.0) {x*=0.001;dimension--;}
    while(x >= 10.0) {
      x *= 0.1;
      dez_punkt++;
    }
    x += RUNDUNG;                           // runden
    if(x >= 10.0) {                         // Ueberlauf bei Rundung
      x *= 0.1;                             // korrigieren
      dez_punkt++;
      if(dez_punkt > 2) {                   // Ueberlauf der Dimension
        dimension--;
        dez_punkt = 0;
      }
    }
  } else {
    x = 0.0; dez_punkt=0; dimension=3;      // 0.00000 Hz ausgeben
  }
  for(i=0;i<STELLEN;i++) {
    j = (char)x;
    *s++ = (j+'0');
    if(i == dez_punkt) *s++ = ('.');
    x -= j;
    x *= 10;
  }
  *s++ = (' ');
  p = f_dim[dimension];
  while(*p) *s++ = *p++;                    // Dimension anhängen
  *s++ = 0;                                 // string abschliessen
}

// % Werte von 0.00001 - 99.9999 Wandeln
void zeige_prozent(char *header, double x, char *s)
{
int8_t i,j,dez_punkt,dimension;
char *p;

  dez_punkt = 0;
  dimension = 3;
  p = header;
  while(*p) *s++ = *p++;
  if(x >= 0.00001) {
    while(x >= 10.0) {
      x *= 0.1;
      dez_punkt++;
    }
    x += RUNDUNG;                           // runden
    if(x >= 10.0) {                         // Ueberlauf bei Rundung
      x *= 0.1;                             // korrigieren
      dez_punkt++;
      if(dez_punkt > 2) {                   // Ueberlauf der Dimension
        dimension--;
        dez_punkt = 0;
      }
    }
  } else {
    x = 0.0; dez_punkt=0; dimension=3;      // 0.00000  ausgeben
  }
  for(i=0;i<STELLEN;i++) {
    j = (char)x;
    *s++ = (j+'0');
    if(i == dez_punkt) *s++ = ('.');
    x -= j;
    x *= 10;
  }
  *s++ = (' '); *s++ = ('%'); *s++ = (' '); *s++ = (' ');
  *s++ = 0;                                 // string abschliessen
}



void init_t8(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8, ENABLE);  // Timer8 aktivieren
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); // PortC aktivieren

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_Init(GPIOC, &GPIO_InitStructure);

  // PC6, PC7, PC8 und PC9 als capture-input zu T8
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource6, GPIO_AF_TIM8);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource7, GPIO_AF_TIM8);   // nicht verwendet
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource8, GPIO_AF_TIM8);   // nicht verwendet
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource9, GPIO_AF_TIM8);   // nicht verwendet

  NVIC_EnableIRQ(TIM8_CC_IRQn);                 // Int-T8 aktivieren
  NVIC_SetPriority(TIM8_CC_IRQn,0x8);
  NVIC_EnableIRQ(TIM8_UP_TIM13_IRQn);           // Int-T8 aktivieren
  NVIC_SetPriority(TIM8_UP_TIM13_IRQn,0x8);

  TIM8->CCMR1 = TIM_CCMR1_IC1F_1 | TIM_CCMR1_IC1F_0 |
                TIM_CCMR1_IC2F_1 | TIM_CCMR1_IC2F_0 |   // beide Eingaenge leicht filtern
                TIM_CCMR1_CC1S_0 | TIM_CCMR1_CC2S_1;    // capture zuordnung IC1 -> TI1, IC2 -> TI2
  TIM8->CCER =  TIM_CCER_CC1E | TIM_CCER_CC2E |         // capture Freigabe
                TIM_CCER_CC2P;                          // neg. Flanke an IC2
  TIM8->DIER |= TIM_DIER_CC1IE | TIM_DIER_CC2IE | TIM_DIER_UIE; // mit interrupts
  TIM8->CR1 |= TIM_CR1_CEN;                     // T8 starten
}


void TIM8_UP_TIM13_IRQHandler(void)
{
  if(TIM8->SR & TIM_SR_UIF) {                   // nur bei überläufen von T8
     TIM8->SR = ~TIM_SR_UIF;
     ueberlauf++;
     mess_dauer++;
  }
}

void TIM8_CC_IRQHandler(void)
{
static uint32_t anzahl, anzahl_alt, zeit, zeit_alt, zeit_1, zeit_2;
uint16_t temp;
static uint8_t cc1_flag = 1;                    // nach CC1 muss zuerst CC2 bearbeitet werden
  if(cc1_flag && TIM8->SR & TIM_SR_CC1IF) {     // positive Flanken
     TIM8->SR = ~TIM_SR_CC1IF;
     cc1_flag = 0;                              // nur CC2 erlauben
     anzahl++;
     temp = TIM8->CCR1;
     zeit = temp + ueberlauf * 0x10000;         // capture-reg lesen: untere 16bit
     if((TIM8->SR & TIM_SR_UIF) && (temp < 0x8000))    // evtl. Ueberlauf T8 noch offen?
        zeit += 0x10000;                        // nur, wenn capture-int + overflow-int gleichzeitig !

    if(mess_status == AUSLESEN) {               // Ergebnisse synchron zum Eingangsimpuls auslesen+ablegen
      mess_ereignisse = anzahl - anzahl_alt;    // Anzahl der Impulse
      anzahl_alt = anzahl;                      // fuer naechste Differenzbildung
      mess_zeit_1 = zeit - zeit_alt;            // eff. Messzeit
      zeit_alt = zeit;                          // fuer naechste Differenzbildung
      mess_zeit_2 = zeit_2;                     // Anteil der pos. Phase
      zeit_2 = 0;                               // wird wieder geloescht
      mess_status = AUSWERTEN;                  // Daten fertig fuer Auswertung
    }
  }
  if(TIM8->SR & TIM_SR_CC2IF) {                 // neg. Flanke, Ende der +Puslweite
     TIM8->SR = ~TIM_SR_CC2IF;
     cc1_flag = 1;                              // wieder CC1 zulassen
     temp = TIM8->CCR2;
     zeit_1 = temp + ueberlauf * 0x10000;       // capture-reg lesen: untere 16bit
     if((TIM8->SR & TIM_SR_UIF) && (temp < 0x8000))    // evtl. Ueberlauf T8 noch offen?
        zeit_1 += 0x10000;                      // nur, wenn capture-int + overflow-int gleichzeitig !
     zeit_2 += zeit_1 - zeit;                   // pos. Pulsweiten addieren
  }
}

void f1_messung(void)
{
static uint8_t  init = 0;
char s[50];
    if(mess_dauer >= ABBRUCH) init = 0;
    if(!init) {
        mess_dauer = 0;
        mess_status = MESSEN;
        messwert_vorhanden = 0;                 // 1. ergebnis verwerfen
        init = 1;
        lcd_zeile1("Fin: -  kein  - ");         // Anzeige bei init = 0
        lcd_zeile2("%PW: - Signal - ");         // oder beim Timeout
        lcd_zeile3("+PW: ---------- ");         // oder beim Timeout
        lcd_zeile4("-PW: ---------- ");         // oder beim Timeout
    }
    if(mess_status==MESSEN  && mess_dauer >= MESSZEIT) {
      mess_status=AUSLESEN;                     // Auswertung starten
    }
    if(mess_status==AUSWERTEN) {
      mess_status = MESSEN;                     // neu starten
      mess_dauer = 0;                           // und Messzeit abwarten
      if(messwert_vorhanden) {
        frequenz = ((double)mess_ereignisse * ref_frequenz) / mess_zeit_1;  // Frequenz berechnen
        zeige_x("Fin: ",frequenz, 0, s);
        lcd_zeile1(s);
        zeige_prozent("%PW: ", (double)mess_zeit_2/(double)mess_zeit_1 * 100.0,s);
        lcd_zeile2(s);
        zeige_x("+PW: ", ((double)mess_ereignisse * ref_frequenz) / mess_zeit_2,1,s);
        lcd_zeile3(s);
        zeige_x("-PW: ", ((double)mess_ereignisse * ref_frequenz) / (mess_zeit_1-mess_zeit_2),1,s);
        lcd_zeile4(s);
      }
      else messwert_vorhanden = 1;              // sperre wieder aufheben
    }
}

int main(void)
{
// System ist initialisiert und laeuft mit 168MHz
  lcd_init();
  init_t8();                                    // T8 freilaufend mit 168MHz im capture-modus starten
  __enable_irq();
  while(1) {
    f1_messung();
  }
}
