/* Reziproke Frequenzmessung mit STM32H750 mit 8-stelliger Auflösung. Eingangsfrequenbereich: 0,1 Hz - 100 MHz Die Frequenz wird aus den Eingangsimpulsen/Zeit errechnet. Dazu werden die Timer15 und Timer16 verwendet. Als Zeitbasis für alle Messungen dient die interne Frequenz SystemTimerClock mit (hier) 240 MHz. Timer15 zählt am Eingang TIM15_CH1 (PE5) forlaufend die Eingangsimpulse. Timer16 zählt fortlaufend die interen Impulse von SystemTimerClock (240 MHz). Beide Zähler werden einmalig gestartet und dann weder gestoppt noch gelöscht. Timer6 erzeugt das Timing für die Messdauer im 1 ms Raster. Ein ext. D-FF erzeugt auf Anforderung mit Signal TRIG2 (PC0) synchron zu einem Eingangsimpuls das Signal CAP2 (TIM15_CH2 (PE6) für Timer15 und TIM16_CH1 (PB8) für Timer16). In den zugehörigen Capture-Registern werden die Anzahl der Ereignisse sowie deren genauer Zeitpunkt festgehalten. Zusätzlich werden auch die Überläufe der Timer berücksichtigt, sodaß beide Messwerte als uint32_t Variablen zur Auswertung bereitstehen: "F2_end_ereignis" und "F2_end_zeit". Diese Zeitpunkte werden in der ISR TIM15_IRQHandler ermittelt. Ein einzelner Messpunkt liefert noch kein Ergebnis, sondern es müssen ein Startzeitpunkt und ein Endzeitpunkt vorliegen, um aus deren Differenz Ereignisse und Zeit zu erhalten: "F2_mess_ereignisse" und "F2_mess_zeit". "F2_mess_zeit" enthält die Anzahl der internen Referenzimpulse und muß daher noch mit Division durch SystemTimerClock auf Sekunde umgerechnet werden. Das Ergebnis ist dann "eff_zeit2". Das Messergebnis ergibt sich dann als: "F2_ergebnis = F2_mess_ereignisse / eff_zeit2". Nach einer Auswertung werden die letzten Endpunkte zu den Startpunkten für das neue Intervall. Wie geschrieben wird eine Auswertung immer mit dem Signal TRIG2 angefordert und synchron zu einem Eingangsimpuls gestartet. Wenn man schnell viele Messwerte braucht, könnte man nach jeder Auswertung mit TRIG2 gleich eine neue Auswertung anfordern. Der hier verwendete µC STM32H750 schafft so einige 100000 Messungen/s, sofern man auf die ser. Datenausgabe verzichtet. Benötigt man jedoch eine möglichst hohe Auflösung der Messwerte, muß man dafür sorgen, daß die Zeitmessung möglichst hoch aufgelöst ist. Dies gilt nicht für die Eingangsimpulse, da diese den Zeitpunkt der Auswertung synchron vorgeben. Die Zeitmessung läuft mit der internen Frequenz SystemTimerClock. Die hier eingestellte Maximalfrequenz beträgt 240 MHz oder anders geschrieben 2.40E08 Hz. Am Exponenten ist zu erkennen, daß in einer Sekunde eine 8-stellige Auflösung bei der Zeitmessung vorliegt. Selbst bei 1/2 s Messzeit sind mit 1.20E08 Referenzimpulsen noch 8-stellige Werte erreichbar. Für ein 8-stelliges Messergebnis sind die Auswertungen mit einem Abstand von >= 0,5 s anzufordern. Verkürzt man diese Zeit auf z.B. 1 ms, geht die Auflösung auf 5 Stellen/Messwert zurück. Da jede Auswertung synchron zu einem Eingangsimpuls gestartet wird, wird die Messrate bei niedrigen Frequenzen reduziert. Bei 1 Hz Eingangssignal ist nur 1 Messung/s möglich. Fehlt das Eingangssignal ganz, wird per Timeout die Messung abgebrochen und neu gestartet. Die Messwerte werden über USART3 ausgegeben: "sende_sting(s)" 2020-10-21 Michael Nowak http://www.mino-elektronik.de Alle Angaben ohne Gewaehr ! 2021-05-03: Zusätzlich wird das Tastverhältnis am Signaleingang PE5 gemessen. Dazu wird per TIM7-ISR der Eingang gelesen und neben der Anzahl der Abtastungen "pwm_sample_sum" auch die Anzahl der positiven Zustände in der Variable "pwm_sample_positiv" gezählt. Daraus ergibt sich das Tastverhältnis aus "pwm_sample_positiv / pwm_sample_sum", welches als "pwm_ergebnis" verwendet wird. Da der Eigang PE5 per Hardware invertiert ist, muß auch das Tastverhältnis invertiert werden: "pwm_ergebnis = 1.0 - pwm_ergebnis". Zur Ausgabe als prozentualer Wert wird der angezeigte Wert mit 100.0 skaliert und dem %-Zeichen versehen. zur TIM7-ISR: Diese wird mit maximal 3 MHz aufgerufen. Damit keine Synchronität zum Eingangssignal auftritt, wird die Abtastfrequenz mit Zufallszahlen aus RNG moduliert. Um die Abtastfrequenz hoch zu halten, wird RNG mit RNG_MASKE auf Werte von 0 - 63 begrenzt. Die längste Abtastperiode beträgt daher (240/3 + 63), was 1,678 MHz entspricht. Die zufällige Abtastrate liegt somit im Bereich 1,678 - 3,000 MHz. */ #define F2_MESSUNG #include #include #include "stm32h7xx.h" extern void sende_string(char *s); // Ausgabe über USART3 extern void lcd_zeile1(char *s); extern void lcd_zeile2(char *s); extern uint8_t lcd_init(void); extern uint32_t SystemTimerClock; #define SYSTEM_TAKT 240 // in MHz #define DEF_F2_TAKT (SYSTEM_TAKT * 1000000) // 240 MHz #define DEF_F2_MESSZEIT 666 // 0,666 s #define DEF_TIMEOUT 10000 // nach 10 s #define MAX_ZEICHEN 50 // fuer Ergebnis String #define MAX_SAMPLE_RATE 3 // in MHz #define RNG_MASKE 63 // nur untere Bits verwenden #define MIN_SAMPLE_INTERVALL (SYSTEM_TAKT/MAX_SAMPLE_RATE-1) // ergibt 3 MHz ISR @ 240 MHz #define PE5 5 // Signaleingang #define PRIO_TIMER 4 // hohe Priorität für T15 und T16 #define PRIO_TIM6 10 // niedrig, unkritisch #define PRIO_TIM7 1 // sehr hoch #define AFR_TIM15 4 // für T15 lt. Datenblatt #define AFR_TIM16 1 // für T16 lt. Datenblatt #define BIT(x) (1<BSRR = (SET_GPIO << number); // IO-Bit setzen } else { GPIOx->BSRR = (RESET_GPIO << number); // IO-Bit loeschen } } // 1 kHz ISR mit T6 erzeugen void init_t6(void) { static uint8_t init; if(!init) { init = 1; RCC->APB1LENR |= RCC_APB1LENR_TIM6EN; NVIC_EnableIRQ(TIM6_DAC_IRQn); NVIC_SetPriority(TIM6_DAC_IRQn,15); // geringe Prioritaet TIM6->PSC = SystemTimerClock/1000000-1; // Vorteiler auf 1 MHz TIM6->ARR = 1000-1; // Teiler für 1ms TIM6->CR1 = TIM_CR1_CEN; // starten TIM6->DIER = TIM_DIER_UIE; // und Interrupt freigeben } } void TIM6_DAC_IRQHandler(void) // Aufruf mit 1 kHz { if(TIM6->SR & TIM_SR_UIF) { // nur bei überläufen von T16 TIM6->SR = ~TIM_SR_UIF; F2_mess_dauer++; // Messzeit erfassen __ISB(); // gelöschtes Flag abwarten } } // 1,6 - 3 MHz ISR mit T7 erzeugen void init_t7(void) { static uint32_t alter_takt; if(alter_takt != SystemTimerClock) { // geänderte Frequenz RCC->APB1LENR |= RCC_APB1LENR_TIM7EN; alter_takt = SystemTimerClock; TIM7->CR1 = 0; // stoppen TIM7->ARR = MIN_SAMPLE_INTERVALL; // max. Abfragefrequenz TIM7->DIER = TIM_DIER_UIE; // und Interrupt freigeben NVIC_EnableIRQ(TIM7_IRQn); NVIC_SetPriority(TIM7_IRQn,PRIO_TIM7); // recht hohe Prioritaet } TIM7->CR1 |= TIM_CR1_CEN | TIM_CR1_ARPE; // starten } // PE5 abtasten und pos. Signalanteil zählen void TIM7_IRQHandler(void) // Aufruf mit variabler Frequenz { TIM7->SR = ~TIM_SR_UIF; pwm_sample_sum++; // Anzahl immer erhöhen if(GPIOE->IDR & BIT(PE5)) pwm_sample_positiv++; // nur positiver Anteil if(RNG->SR & RNG_SR_DRDY) // nächste Periode von RNG abhängig TIM7->ARR = (RNG->DR & RNG_MASKE) + MIN_SAMPLE_INTERVALL; } void hole_pwm_werte(uint32_t *sample, uint32_t *positiv) { TIM7->SR &= ~TIM_CR1_CEN; // TIM7 sperren *sample = pwm_sample_sum; // beide Werte lesen *positiv = pwm_sample_positiv; TIM7->CR1 |= TIM_CR1_CEN; // und TIM7 wieder freigeben } // Timer15 zählt die ext. Eingangsimpulse: Ereignismessung void init_t15(void) { uint32_t tmp; RCC->APB2ENR |= RCC_APB2ENR_TIM15EN; RCC->AHB4ENR |= RCC_AHB4ENR_GPIOEEN; // PortE aktivieren NVIC_EnableIRQ(TIM15_IRQn); // Int-T15 aktivieren NVIC_SetPriority(TIM15_IRQn,PRIO_TIMER); TIM15->CR1 = 0; // T15 stoppen, falls schon aktiv tmp = GPIOE->MODER; tmp &= ~(CLR_MODER << (TIM15_ZAEHLER2 * 2) | CLR_MODER << (TIM15_CAPTURE2 * 2)); // MODER auf 0 tmp |= (AF << (TIM15_ZAEHLER2 * 2) | // altern. Funktion AF << (TIM15_CAPTURE2 * 2)); GPIOE->MODER = tmp; // PE5 als Zählereingang, PE6 als capture-input zu T15 GPIOE->AFR[0] |= (AFR_TIM15 << (TIM15_ZAEHLER2 * 4) | AFR_TIM15 << (TIM15_CAPTURE2 * 4)); TIM15->CCMR1 = TIM_CCMR1_CC2S_0; // capture zuordnung IC2 -> TI2 TIM15->CCER = TIM_CCER_CC2E; // pos. Flanken an IC2 freigeben TIM15->SMCR |= TIM_SMCR_TS_2 + TIM_SMCR_TS_0; // ext. Signal von TI1 TIM15->SMCR |= TIM_SMCR_SMS_2 + TIM_SMCR_SMS_1 + TIM_SMCR_SMS_0; // Zählereingang von TI1 TIM15->DIER |= TIM_DIER_CC2IE + TIM_DIER_UIE; // mit Capture + Überlauf interrupts TIM15->CR1 |= TIM_CR1_CEN; // T15 starten } // Timer16 zählt die internen Referenzimpulse: Zeitmessung void init_t16(void) { uint32_t tmp; RCC->APB2ENR |= RCC_APB2ENR_TIM16EN; RCC->AHB4ENR |= RCC_AHB4ENR_GPIOBEN; // PortB aktivieren NVIC_EnableIRQ(TIM16_IRQn); // Int-T16 aktivieren NVIC_SetPriority(TIM16_IRQn,PRIO_TIMER); TIM16->CR1 = 0; // T16 stoppen, falls schon aktiv tmp = GPIOB->MODER; tmp &= ~(CLR_MODER << (TIM16_CAPTURE2 * 2)); // MODER PB8 = 0 tmp |= (2 << (TIM16_CAPTURE2 * 2)); // PB8 auf Ausgang GPIOB->MODER = tmp; // PB8 als capture-input zu T16 GPIOB->AFR[1] |= (AFR_TIM16 << ((TIM16_CAPTURE2-8) * 4)); TIM16->CCMR1 = TIM_CCMR1_CC1S_0; // capture zuordnung IC1 -> TI1 TIM16->CCER = TIM_CCER_CC1E; // pos. Flanken an IC1 freigeben TIM16->DIER |= TIM_DIER_UIE; // mit interrupt bei Überlauf TIM16->CR1 |= TIM_CR1_CEN; // T16 starten } // nur Überlaufe zählen void TIM16_IRQHandler(void) { if(TIM16->SR & TIM_SR_UIF) { // nur bei überläufen von T16 TIM16->SR = ~TIM_SR_UIF; F2_zeit_ueberlauf += 0x10000; } __ISB(); // gelöschtes Flag abwarten } // ISR zu Timer15 // synchron zu einem Eingangsimpuls Ereignisse und Zeitpunkt ermitteln // anschließend Auswertung freigeben // ferner Überlaufe bei der Ereigniszählung zählen // Auswertung Einzel-/Gesamtmessung synchron zu Capture-Signal an PE6: D-FF wurde gesetzt void TIM15_IRQHandler(void) { uint32_t temp; uint32_t pwm_temp_s, pwm_temp_p; // temporäre Werte der PWM if(TIM15->SR & TIM_SR_CC2IF) { // bei Capture-Ereignis TIM15->SR = ~TIM_SR_CC2IF; // flag wieder loeschen temp = TIM15->CCR2; // Capture-Wert Ereignisse F2_end_ereignis = F2_ueberlauf + temp; // Endwert Ereignisse if((TIM15->SR & TIM_SR_UIF) && (temp < 0x8000)) // evtl. Ueberlauf T15 noch offen? F2_end_ereignis += 0x10000; // nur, wenn capture-int + overflow-int gleichzeitig ! temp = TIM16->CCR1; // Capture-Wert Zeit F2_end_zeit = F2_zeit_ueberlauf + temp; // Endwert Zeit if((TIM16->SR & TIM_SR_UIF) && (temp < 0x8000)) { // evtl. Ueberlauf T16 noch offen? F2_end_zeit += 0x10000; // dann korrigieren } // PWM auswerten hole_pwm_werte(&pwm_temp_s, &pwm_temp_p); // neue Werte lesen pwm_summe = pwm_temp_s - pwm_summe_alt; // Differenzen bilden pwm_summe_alt = pwm_temp_s; pwm_positiv = pwm_temp_p - pwm_positiv_alt; pwm_positiv_alt = pwm_temp_p; pwm_ergebnis = (double)pwm_positiv/(double)pwm_summe; // und Verhältnis berechnen set_pin(GPIOC, TRIG2, 0); // Trigger-Ausgang wieder auf '0' F2_mess_status = AUSWERTEN; // Daten fertig fuer Auswertung } if(TIM15->SR & TIM_SR_UIF) { // bei überläufen von T15 TIM15->SR = ~TIM_SR_UIF; // flag wieder loeschen F2_ueberlauf += 0x10000; // und Überläufe zählen } __ISB(); // gelöschte Flags abwarten } void starte_rng() { RCC->AHB2ENR |= RCC_AHB2ENR_RNGEN; RCC->CR |= RCC_CR_HSI48ON; RNG->CR |= RNG_CR_RNGEN; } /* direkte Messung mit internen Timern mit max. 8 Stellen/s Timer15 zählt die externen Impulse Timer16 mißt die Zeit mit internem Takt */ void F2_messung(uint8_t init_timer) { static uint8_t init = 0; static char s[MAX_ZEICHEN]; if(init_timer) { lcd_init(); init = 0; // Anzeige + Messung initialisieren init_t6(); // T6 für Messdauer in 1 ms Schritten init_t7(); // zur Abtastung des Eingangs starte_rng(); // Zufallsgenerator aktivieren init_t15(); // T15 als Ereignisszähler mit capture-modus starten init_t16(); // T16 als Referenz-Zähler RCC->AHB4ENR |= RCC_AHB4ENR_GPIOCEN; // PortC aktivieren GPIOC->MODER &= ~(CLR_MODER << TRIG2*2); // neutral und dann GPIOC->MODER |= OUT << TRIG2*2; // als Trigger-Ausgang GPIOC->OSPEEDR |= V_HIGH_SPEED << TRIG2*2; // mit steilen Flanken } if(!init) { F2_mess_status = MESSEN; // mit MESSEN Beginnen F2_mess_dauer = F2_messzeit; // und 1. Messung sofort abschließen F2_messwert_vorhanden = 0; // dabei 1. Ergebnis verwerfen lcd_zeile1("F: kein Signal "); lcd_zeile2("PWM: "); init = 1; } if(F2_mess_dauer >= F2_timeout) { // bei fehlenden Eingangsimpulsen set_pin(GPIOC, TRIG2, 0); // Trigger-Ausgang wieder auf '0' sende_string("kein Signal"); init = 0; // neu initialisieren } if(F2_mess_status == MESSEN && F2_mess_dauer >= F2_messzeit) { F2_mess_status = AUSLESEN; // Zähler bei nächstem Eingangsimpuls auslesen set_pin(GPIOC, TRIG2, 1); // Trigger-Ausgang auf '1' } if(F2_mess_status == AUSWERTEN) { // Ereignisse und Zeitpunkt ermittelt F2_mess_zeit = F2_end_zeit - F2_start_zeit; // Zeit-Differenz bilden F2_start_zeit = F2_end_zeit; F2_mess_ereignisse = F2_end_ereignis - F2_start_ereignis; // Impuls-Differenz F2_start_ereignis = F2_end_ereignis; // fuers naechste Intervall F2_mess_status = MESSEN; // neu starten F2_mess_dauer = 0; // und Messzeit abwarten if(F2_messwert_vorhanden) { // Ergebnis berechnen und anzeigen/ausgeben F2_ref_frequenz = SystemTimerClock + F2_ref_offset; // immer aktuellen Wert verwenden eff_zeit2 = (double)(F2_mess_zeit ) / F2_ref_frequenz; // Zeit in Sekunden F2_ergebnis = ((double)F2_mess_ereignisse) / eff_zeit2; // Frequenz berechnen sprintf(s,"F: %13.7E",F2_ergebnis); // 8-stelliges Ergebnis im Exp-Format lcd_zeile1(s); sende_string(s); // PWM in 2.Zeile anzeigen // Achtung: PE5-Eingang ist invertiert, daher auch PWM-Wert invertieren pwm_ergebnis = 1.0 - pwm_ergebnis; sprintf(s,"PWM: %8.4f %",pwm_ergebnis*100.0); // auf 100% skalieren lcd_zeile2(s); sende_string(s); } else F2_messwert_vorhanden = 1; // sperre wieder aufheben } }