Forum: Mikrocontroller und Digitale Elektronik STM32 Frequenzmessung im Capturemodus


von Rolf D. (rolfdegen)


Angehängte Dateien:

Lesenswert?

Hallöchen..

Ich benutze einen STM32F103C8T6 für eine Frequenzmessung im Bereich  von 
1Hz-10Khz. Leider habe ich das Problem, das es sporadisch zu falschen 
Messergebnissen kommt. In die Berechnung ist der Capture_overflow 
Counter mit einbezogen und dieser scheint auch das Problem zu sein. Ab 
und an gibt es einen Überlauf obwohl die Frequenz > 20 Hz in der 
Berechnung keinen Überlauf erzeugen sollte. Das zweite Bild zeigt ein 
falsches Ergebnis bei einer Messfrequenz von 100Hz.

Timer Init Werte:
Prescaler : 71
Counter Period : 49999
Messfrequenz: 100Hz

Setze ich den Capture_overflow Counter in der Berechnung auf 0 ist der 
Messwert oberhalb von 20Hz stabiel und ok.

Hab mal ein kleines Filmchen gemacht und auf Youtube hochgeladen.

Link: https://youtu.be/nyP7hJJcSww

Die Messfrquenz im Video beträgt 100Hz.

1
/* Includes ------------------------------------------------------------------*/
2
#include "main.h"
3
#include "i2c.h"
4
#include "tim.h"
5
#include "gpio.h"
6
7
/* Private includes ----------------------------------------------------------*/
8
/* USER CODE BEGIN Includes */
9
#include "stm32_hal_legacy.h"
10
#include "ssd1306.h"
11
#include "string.h"
12
#include "stdio.h"
13
#include <stdbool.h>
14
15
/* USER CODE END Includes */
16
17
/* Private typedef -----------------------------------------------------------*/
18
/* USER CODE BEGIN PTD */
19
20
/* USER CODE END PTD */
21
22
/* Private define ------------------------------------------------------------*/
23
/* USER CODE BEGIN PD */
24
25
/* USER CODE END PD */
26
27
/* Private macro -------------------------------------------------------------*/
28
/* USER CODE BEGIN PM */
29
30
/* USER CODE END PM */
31
32
/* Private variables ---------------------------------------------------------*/
33
34
/* USER CODE BEGIN PV */
35
36
volatile uint8_t overflow_cnt = 0;
37
volatile bool update_Display = false;
38
volatile uint16_t input_capture1 = 0;
39
volatile uint16_t input_capture2 = 0;
40
41
/* USER CODE END PV */
42
43
/* Private function prototypes -----------------------------------------------*/
44
void SystemClock_Config(void);
45
/* USER CODE BEGIN PFP */
46
47
/* USER CODE END PFP */
48
49
/* Private user code ---------------------------------------------------------*/
50
/* USER CODE BEGIN 0 */
51
52
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
53
{
54
  static uint8_t fmessState = 0;
55
56
  switch (fmessState) {
57
          case 0:
58
            input_capture1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); //1)
59
            overflow_cnt = 0;
60
            update_Display = false;
61
            fmessState = 1;
62
            HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
63
            break;
64
          case 1:
65
            input_capture2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); //1)
66
            update_Display = true;
67
            fmessState = 0;
68
            break;
69
70
          default:
71
          break;
72
    }
73
}
74
75
76
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
77
78
  overflow_cnt++;   // inc overflow_counter
79
80
  HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);  // LED indicator
81
82
}
83
84
/* USER CODE END 0 */
85
86
/**
87
  * @brief  The application entry point.
88
  * @retval int
89
  */
90
int main(void)
91
{
92
  /* USER CODE BEGIN 1 */
93
94
  /* USER CODE END 1 */
95
  
96
97
  /* MCU Configuration--------------------------------------------------------*/
98
99
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
100
  HAL_Init();
101
102
  /* USER CODE BEGIN Init */
103
104
  /* USER CODE END Init */
105
106
  /* Configure the system clock */
107
  SystemClock_Config();
108
109
  /* USER CODE BEGIN SysInit */
110
111
  /* USER CODE END SysInit */
112
113
  /* Initialize all configured peripherals */
114
  MX_GPIO_Init();
115
  MX_TIM2_Init();
116
  MX_I2C1_Init();
117
  /* USER CODE BEGIN 2 */
118
  HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); // 6)
119
  HAL_TIM_Base_Start_IT(&htim2); // 7)
120
  HAL_NVIC_EnableIRQ(TIM2_IRQn);
121
122
  ssd1306_Init();
123
  ssd1306_Fill(Black);
124
  ssd1306_Rect(0,16,127,63, White);
125
  ssd1306_SetCursor(0,0);
126
  ssd1306_WriteString("Freq:", Font_7x10, White);
127
  ssd1306_UpdateScreen();
128
  /* USER CODE END 2 */
129
130
  /* Infinite loop */
131
  /* USER CODE BEGIN WHILE */
132
  while (1)
133
  {
134
    /* USER CODE END WHILE */
135
136
    /* USER CODE BEGIN 3 */
137
    int32_t zw_Erg;
138
    uint32_t pw_us;
139
    uint16_t count_;
140
    uint16_t max_cnt = 49999;
141
    double pw_s;
142
    uint16_t frq_Hz;
143
    char frq_str[20];
144
145
    if (update_Display == true){
146
147
      update_Display = false;
148
      zw_Erg = input_capture2 - input_capture1;
149
150
151
      if (zw_Erg >= 0){
152
        pw_us = zw_Erg;
153
      }
154
      else{
155
        pw_us = zw_Erg + max_cnt;
156
      }
157
158
     pw_us = pw_us + (overflow_cnt * max_cnt);
159
      pw_s = (double)pw_us / 1E6;
160
      frq_Hz = 1 / pw_s;
161
162
      uint16_t frequenz = (uint32_t)(frq_Hz * 1000) / 1000;
163
164
         sprintf(frq_str, "Frq: %2d", frequenz);
165
         ssd1306_SetCursor(10,25);
166
         ssd1306_WriteString("                  ",Font_7x10,White);
167
         ssd1306_SetCursor(20,25);
168
          ssd1306_WriteString(frq_str,Font_7x10,White);
169
170
        sprintf(frq_str, "ov_cnt: %2d", overflow_cnt);
171
        ssd1306_SetCursor(20,45);
172
        ssd1306_WriteString("                  ",Font_7x10,White);
173
        ssd1306_SetCursor(20,45);
174
        ssd1306_WriteString(frq_str,Font_7x10,White);
175
176
        ssd1306_UpdateScreen();
177
178
        HAL_Delay(100);
179
180
       }
181
  }
182
  /* USER CODE END 3 */
183
}

Ich habe den Original Code aus dem Script "Einführung in C für STM32" 
von Prof. Dr. -Ing. Otto Parzhuber Hochschule München FK 06 für mein 
Projekt etwas angepasst.

Link zum Script: 
http://dodo.fb06.fh-muenchen.de/parzhuber/SKRIPT_C_STM32.pdf

Gruß Rolf

: Bearbeitet durch User
von Sbl (Gast)


Lesenswert?

Hallo Rolf,

da kommen jetzt ein paar Sachen zusammen.

1.)
Ich würde beim STM32 für so eine Aufgabe einen 32Bit Zähler nehmen. Dann 
hast du die Probleme schon mal gar nicht.
Du hast bis auf wenige Fälle keinen Überlauf. Wenn doch, dann ist 
input_capture2 kleiner als input_capture1 und es reicht die Abfrage wie 
auch in deinem Beispiel.


2.)
Ich bin jetzt wieder beim 16Bit Counter wie in deinem Projekt.
Gibt es einen Ûberlauf, ist input_capture2 kleiner als input_capture1, 
die Differenz ist negativ. Das berücksichtigst Du ja schon hier:
1
      if (zw_Erg >= 0){
2
        pw_us = zw_Erg;
3
      }
4
      else{
5
        pw_us = zw_Erg + max_cnt;
6
      }

aber du berücksichtigst den Überlauf später nochmals, nämlich in
1
pw_us = pw_us + (overflow_cnt * max_cnt);

das führt dann wohl zu einem falschen Ergebnis.
Ich meine, du müsstest die erste Abfrage auf kleine 0 weglassen.


3.)
Wenn Du das aus 2.) korrigiert hast und es gehen sollte, dann gibt es 
immer noch den viel Fall, dass dein Hauptprogramm input_capture1 und 
input_capture2 liest, kurz danach der Überlauf kommt und overflow_cnt 
dann eins hoch zählt. Der Fall ist seltener, aber er wird vorkommen.
Dafür gibt es verschiedenen Lösungen.
Beispielsweise könntest du vor dem Lesen von input_capture1 und 
input_capture2 den Wert overflow_cnt lesen und danach nochmals. Hat er 
sich inzwischen geändert, dann musst du input_capture1 und 
input_capture2 nochmals lesen.


sbl

von m.n. (Gast)


Lesenswert?


von Rolf D. (rolfdegen)


Lesenswert?

Hallo Sbl..

Danke für deine Tips.

Ich hab die CodeZeile "pw_us = pw_us + (overflow_cnt * max_cnt);" mal 
entfernt und das Messergebnis ist oberhalb von 20Hz immer korrekt und 
stabil.

Es schein an der Art des overflow_cnt zu liegen wie dieser im Interrupt 
gehandelt wird. Sobald ein CaptureCallback Interrupt anliegt wird der 
Counter auf 0 gesetzt. Hat aber während dieser Zeit ein Überlauf 
stattgefunden löst danach der PeriodElapsedCallback Interrupt aus und 
incrementiert den overflow_cnt.

von W.S. (Gast)


Lesenswert?

Rolf D. schrieb:
> von Prof. Dr. -Ing. Otto Parzhuber Hochschule München FK 06

Bei diesem Text kommt mir der Groll!

Ist dieser Professor von ST bezahlt?

Zitat:
Inhaltsverzeichnis
1  Erste Schritte in C
2  Übersicht: zulässige Datentypen in C-Programmen
3  Die Zuweisung von Werten bei Variablen
4  Operatoren
5  Schleifen
6  Auswahl
7  C – Anwendungsbeispiel (Alarmanlage)
8  C – Funktionen
9  Zusammengesetzte Datentypen
10  STM32 spezifische Programmierung
10.1  Zugriff auf Register des STM32 Microcontrollers
10.2  Wie kann in der Sprache C auf diese Register zugegriffen werden?
10.3  Registerzugriff in der Sprache C in der Praxis
10.4  Hardware Abstraction Layer (CubeMX-HAL)
10.5  Interrupts
10.6  GPIO-Ports
10.7  Timer
10.8  UART
10.8.1  Senden mit Cube-HAL
10.8.2  Empfangen mit Cube-HAL, interruptgesteuert
10.9  I2C-Bus
...etc etc,
Ende Zitat

Der Autor versucht eine Einführung in C zu schreiben und landet dabei 
sofort bei STM32 und Cube-HAL.

Bei sowas wundert es mich nicht mehr, daß hier in diesem Forum so viele 
Leute mit einem bedauerlich engen Horizont aufschlagen - weil sie nicht 
die Prinzipien gelernt haben, sondern auf STM32 mit Cube und HAL 
getrimmt worden sind.

Als ob die Welt der 32 bittigen µC nur aus STM32 bestünde und als ob man 
ohne die Software von ST einen Mikrocontroller überhaupt nicht 
programmieren könnte.

Das ganze ist schief, tendenziös, ST-lastig und für ein Hochschul-Papier 
geradezu beschämend. Professoren werden aus Steuergeldern bezahlt und 
sollten deshalb keine derartige Firmen-Propaganda betreiben.

Eine Einführung in allgemeines C wäre OK.
Eine Einführung in die Verwendung von C für µC wäre auch OK.
Eine Einführung in Cortex-M wäre auch noch OK, besser wäre eine 
vergleichende Einführung in verschiedene 32 bittige µC - aus Gründen der 
Ausgewogenheit.

Eine Einführung in C auf STM32 mit Cube und HAL ist Firmen-Propaganda. 
Und sie führt nicht dazu, daß die Studenten einen unabhängigen, freien 
und regen Geist entwickeln und die Übersicht über die Thematik gewinnen. 
Es ist eher eine Art Dressur.

Wie gesagt: dieses Papier erregt meinen Groll.

W.S.

von Rolf D. (rolfdegen)


Lesenswert?

Vielleicht hilft es, wenn man im CaptureCallback Interrupt das 
Interrupt_flag für den PeriodElapsedCallback löscht. Ich weis nur nicht, 
wie das dumme Flag Register heisst

von Rolf D. (rolfdegen)


Lesenswert?

Hallöchen..

Ich habe mein Problem lösen können. Ein Flag in den Interrupt Routinen 
verhindert das zufällige Überschreiben der aktuellen Capture- und 
Counterwerte während der Berechnung für die Display Ausgabe. Ferner habe 
ich die überflüssige if.. else.. Zeilen für die Überprüfung des 
Zwischenergebnis entfernt.

Die Frequenzwerte werden jetzt auf dem Display richtig und stabil 
angezeigt. Ich werde im Programm noch eine Mittelwerterfassung 
integrieren, da die höheren Frequenzwerte nicht so stabil sind. 
Eventuell kann man den Prescaler Division Ratio im Timer (Div: 0/2/4/8) 
im Programm automatisch anpassen.

Kleines Video: https://youtu.be/2HHfi0Ipwqk

Geänderter Code für die Frequenzmessung mit STM32F103C8Tx
1
/* Includes ------------------------------------------------------------------*/
2
#include "main.h"
3
#include "i2c.h"
4
#include "tim.h"
5
#include "gpio.h"
6
7
/* Private includes ----------------------------------------------------------*/
8
/* USER CODE BEGIN Includes */
9
#include "ssd1306.h"
10
#include "string.h"
11
#include "stdio.h"
12
#include <stdbool.h>
13
14
/* USER CODE END Includes */
15
16
/* Private typedef -----------------------------------------------------------*/
17
/* USER CODE BEGIN PTD */
18
19
/* USER CODE END PTD */
20
21
/* Private define ------------------------------------------------------------*/
22
/* USER CODE BEGIN PD */
23
24
/* USER CODE END PD */
25
26
/* Private macro -------------------------------------------------------------*/
27
/* USER CODE BEGIN PM */
28
29
/* USER CODE END PM */
30
31
/* Private variables ---------------------------------------------------------*/
32
33
/* USER CODE BEGIN PV */
34
35
volatile uint8_t overflow_cnt = 0;
36
volatile bool Measurement_flag = true;
37
volatile bool Measurement_flag_2 = true;
38
volatile bool update_Display = false;
39
volatile uint16_t input_capture1 = 0;
40
volatile uint16_t input_capture2 = 0;
41
42
/* USER CODE END PV */
43
44
/* Private function prototypes -----------------------------------------------*/
45
void SystemClock_Config(void);
46
/* USER CODE BEGIN PFP */
47
48
/* USER CODE END PFP */
49
50
/* Private user code ---------------------------------------------------------*/
51
/* USER CODE BEGIN 0 */
52
53
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
54
{
55
  static uint8_t fmessState = 0;
56
57
  if (Measurement_flag == true){    // flag is false during update screen
58
    switch (fmessState) {
59
60
    case 0:
61
        input_capture1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); //1)
62
        overflow_cnt = 0;
63
        update_Display = false;
64
        fmessState = 1;
65
        Measurement_flag_2 = true;
66
        break;
67
    case 1:
68
        input_capture2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); //1)
69
        Measurement_flag_2 = false;
70
        update_Display = true;
71
        fmessState = 0;
72
        break;
73
74
    default:
75
    break;
76
77
    }
78
  }
79
80
81
}
82
83
84
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
85
86
  if (Measurement_flag_2 == true){  // is locked during update screen
87
88
    overflow_cnt++;   // inc overflow_counter
89
  }
90
91
}
92
93
/* USER CODE END 0 */
94
95
/**
96
  * @brief  The application entry point.
97
  * @retval int
98
  */
99
int main(void)
100
{
101
  /* USER CODE BEGIN 1 */
102
103
  /* USER CODE END 1 */
104
  
105
106
  /* MCU Configuration--------------------------------------------------------*/
107
108
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
109
  HAL_Init();
110
111
  /* USER CODE BEGIN Init */
112
113
  /* USER CODE END Init */
114
115
  /* Configure the system clock */
116
  SystemClock_Config();
117
118
  /* USER CODE BEGIN SysInit */
119
120
  /* USER CODE END SysInit */
121
122
  /* Initialize all configured peripherals */
123
  MX_GPIO_Init();
124
  MX_TIM2_Init();
125
  MX_I2C1_Init();
126
  /* USER CODE BEGIN 2 */
127
  HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
128
  HAL_TIM_Base_Start_IT(&htim2);
129
130
  ssd1306_Init();
131
  ssd1306_Fill(Black);
132
  ssd1306_Rect(0,16,127,63, White);
133
  ssd1306_SetCursor(0,0);
134
  ssd1306_WriteString("Freq:", Font_7x10, White);
135
  ssd1306_UpdateScreen();
136
  /* USER CODE END 2 */
137
138
  /* Infinite loop */
139
  /* USER CODE BEGIN WHILE */
140
  while (1)
141
  {
142
    /* USER CODE END WHILE */
143
144
    /* USER CODE BEGIN 3 */
145
146
    uint32_t zw_Erg;
147
    uint16_t max_cnt = 49999;
148
    char frq_str[10];
149
150
    if (update_Display == true){
151
      Measurement_flag = false;        // stop Measurement
152
      //uint16_t input_cap1 = input_capture1;
153
      //uint16_t input_cap2 = input_capture2;
154
      //uint8_t overflow_cnt_ = overflow_cnt;
155
156
      zw_Erg = input_capture2 - input_capture1;          // save captur values
157
158
      uint32_t usec = zw_Erg + (overflow_cnt * max_cnt);     // usec
159
160
161
      uint16_t frequency = 1000000 / usec;  // Hz
162
163
      sprintf(frq_str, "Frq: %2d", frequency);
164
      ssd1306_SetCursor(10,25);
165
      ssd1306_WriteString("                  ",Font_7x10,White);
166
      ssd1306_SetCursor(20,25);
167
        ssd1306_WriteString(frq_str,Font_7x10,White);
168
        ssd1306_UpdateScreen();
169
170
        Measurement_flag = true;    // start Measurement
171
        HAL_Delay(500);
172
173
       }
174
  }
175
  /* USER CODE END 3 */
176
}

Gruß Rolf

von Harry L. (mysth)


Lesenswert?

Du must in allen Callback-Funktionen grundsätlich zuerst überprüfen, ob 
das übergebene Handle auch zu deinem Timer gehört!

In deinem Fall so:
1
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
2
3
  if (htim == &htim2)
4
    {
5
  if (Measurement_flag_2 == true){  // is locked during update screen
6
7
    overflow_cnt++;   // inc overflow_counter
8
  }
9
    }
10
}

So lange du nur 1 Timer verwendest, fällt das nicht weiter auf, aber 
sobald ein weiterer Timer hinzu kommt, fliegt dir das sonst um die 
Ohren.

Das gilt bei HAL grundsätzlich für alle Callbacks!

: Bearbeitet durch User
von Rolf D. (rolfdegen)


Lesenswert?

Hallo Harry

Danke für deinen Hinweis. Da ich zur Zeit nur einen Timer benutze war 
ich ehrlich gesagt zu faul das noch einzubinden. Aber du hast natürlich 
recht :)

Gruß Rolf

von Rolf D. (rolfdegen)


Lesenswert?

Weist du vielleicht wie ich die Werte des Prescaler Division Ratio im 
Timer2 während der Laufzeit ändern kann ?

Ich will damit eine bessere Messgenauigkeit für größere Frequenzen 
erreichen.

: Bearbeitet durch User
von Harry L. (mysth)


Lesenswert?

Rolf D. schrieb:
> Weist du vielleicht wie ich die Werte des Prescaler Division Ratio im
> Timer2 während der Laufzeit ändern kann ?
>
> Ich will damit eine bessere Messgenauigkeit für größere Frequenzen
> erreichen.

Über htim->Instance->xxxx kannst du direkt auf die Register zugreifen.

: Bearbeitet durch User
von Rolf D. (rolfdegen)


Lesenswert?

OK. Danke

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.