STM32 Frequenzmessung im Capturemodus

OP #5993842
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
Angehängte Dateien:
Gast #5993929
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
OP #5993956
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.
Gast #5993957
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.
OP #5994801
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
#5994897
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!

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren