Forum: Mikrocontroller und Digitale Elektronik STM32 Timer PWM Periode dynamisch anpassen


von Hendrik (hendrik2812)


Lesenswert?

Guten Tag,

ich versuche in STM32CubeIDE mit Hilfe von PWM flexible Rechtecksignale 
(es geht nur um TTL, also keine Stromschleife oder ähnliches) im 
zweistelligen Mikrosekundenbereich an einem Port-Pin zu erzeugen. Der µC 
ist ein STM32F411 @50 MHz und ich benutze Timer 11 in Verbindung mit Pin 
PB9. Meine Tests funktionieren halbwegs gut, so lange die Periodendauer 
konstant ist und ich nur die Pulsweite anpasse. Aber selbst dann sind 
der erste Puls und die erste Periode laut Speicheroszi ein paar µS zu 
kurz oder zu lang.

Dies ist der Code für Clock- und Timer-Initialisierung, der aufgrund 
meiner Einstellungen in STM32CubeIDE erzeugt wurde:
1
void SystemClock_Config(void)
2
{
3
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
4
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
5
6
  /** Configure the main internal regulator output voltage
7
  */
8
  __HAL_RCC_PWR_CLK_ENABLE();
9
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
10
11
  /** Initializes the RCC Oscillators according to the specified parameters
12
  * in the RCC_OscInitTypeDef structure.
13
  */
14
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
15
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
16
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
17
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
18
  RCC_OscInitStruct.PLL.PLLM = 4;
19
  RCC_OscInitStruct.PLL.PLLN = 50;
20
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
21
  RCC_OscInitStruct.PLL.PLLQ = 4;
22
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
23
  {
24
    Error_Handler();
25
  }
26
27
  /** Initializes the CPU, AHB and APB buses clocks
28
  */
29
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
30
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
31
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
32
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
33
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
34
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
35
36
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
37
  {
38
    Error_Handler();
39
  }
40
}
41
42
/**
43
  * @brief TIM11 Initialization Function
44
  * @param None
45
  * @retval None
46
  */
47
static void MX_TIM11_Init(void)
48
{
49
50
  /* USER CODE BEGIN TIM11_Init 0 */
51
52
  /* USER CODE END TIM11_Init 0 */
53
54
  TIM_OC_InitTypeDef sConfigOC = {0};
55
56
  /* USER CODE BEGIN TIM11_Init 1 */
57
58
  /* USER CODE END TIM11_Init 1 */
59
  htim11.Instance = TIM11;
60
  htim11.Init.Prescaler = 0;
61
  htim11.Init.CounterMode = TIM_COUNTERMODE_UP;
62
  htim11.Init.Period = 999;
63
  htim11.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
64
  htim11.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
65
  if (HAL_TIM_Base_Init(&htim11) != HAL_OK)
66
  {
67
    Error_Handler();
68
  }
69
  if (HAL_TIM_PWM_Init(&htim11) != HAL_OK)
70
  {
71
    Error_Handler();
72
  }
73
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
74
  sConfigOC.Pulse = 500;
75
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
76
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
77
  if (HAL_TIM_PWM_ConfigChannel(&htim11, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
78
  {
79
    Error_Handler();
80
  }
81
  /* USER CODE BEGIN TIM11_Init 2 */
82
83
  /* USER CODE END TIM11_Init 2 */
84
  HAL_TIM_MspPostInit(&htim11);
85
86
}

Ich benutze ein Array, um die Daten für die weiteren Pulse zu erzeugen:
1
/* USER CODE BEGIN PV */
2
int16_t pwm_tx_array [4][2] = {
3
    { 999, 400 },
4
    { 999, 300 },
5
      { 999, 200 },
6
      { 999, 100 },
7
};
8
int pwm_tx_finished = 0;
9
int16_t pwm_tx_array_counter = 0;
10
int pwm_tx_array_size = sizeof(pwm_tx_array)/sizeof(pwm_tx_array[0]);
11
int16_t pwm_tx_period = 0;
12
int16_t pwm_tx_pulse = 0;
13
/* USER CODE END PV */

Im CallBack für den jeweils aktuellen Puls bereite ich den nächsten Puls 
vor:
1
/* USER CODE BEGIN 4 */
2
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef* htim)
3
{
4
  if (htim->Instance == TIM11){
5
    if (!pwm_tx_finished)
6
    {
7
      HAL_TIM_PWM_Stop_IT(&htim11, TIM_CHANNEL_1);
8
      if (pwm_tx_array_counter == pwm_tx_array_size)
9
      {
10
        pwm_tx_finished = 1;
11
      } else {
12
        pwm_tx_period = pwm_tx_array[pwm_tx_array_counter][0];
13
        pwm_tx_pulse = pwm_tx_array[pwm_tx_array_counter][1];
14
        TIM_OC_InitTypeDef sConfigOC;
15
16
        // htim11.Init.Period = pwm_tx_period;
17
        // HAL_TIM_PWM_Init(&htim11);
18
19
        sConfigOC.OCMode = TIM_OCMODE_PWM1;
20
        sConfigOC.Pulse = pwm_tx_pulse;
21
        sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
22
        sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
23
        HAL_TIM_PWM_ConfigChannel(&htim11, &sConfigOC, TIM_CHANNEL_1);
24
        HAL_TIM_PWM_Start_IT(&htim11, TIM_CHANNEL_1);
25
        ++pwm_tx_array_counter;
26
      }
27
     }
28
  }
29
}
30
/* USER CODE END 4 */

Das funktioniert wie bereits gesagt bis auf die Ungenauigkeit des ersten 
Puls ganz gut, aber sobald ich die 2 Zeilen für die Periode 
einkommentiere und Array-Werte mit wechselnden Perioden benutze, bekomme 
ich echt merkwürdige Kurven auf dem Speicheroszi angezeigt. Zum Testen 
habe ich hier einfach immer kürzere Perioden mit 50:50 Tastverhältnis 
gewählt, es ist aber nur ein Beispiel:
1
/* USER CODE BEGIN PV */
2
int16_t pwm_tx_array [4][2] = {
3
    { 799, 400 },
4
    { 599, 300 },
5
      { 399, 200 },
6
      { 199, 100 },
7
};
8
int pwm_tx_finished = 0;
9
int16_t pwm_tx_array_counter = 0;
10
int pwm_tx_array_size = sizeof(pwm_tx_array)/sizeof(pwm_tx_array[0]);
11
int16_t pwm_tx_period = 0;
12
int16_t pwm_tx_pulse = 0;
13
/* USER CODE END PV */

Der Main Loop ist leer und der µC erzeugt momentan nur diese PWM. Ich 
benötige später nicht wirklich 2 Mikrosekunden lange Pulse und 
eigentlich würden mir +-2 Mikrosekunden Genauikgeit ausreichen, aber da 
ist ja noch das Problem mit der zu ändernden Periode und ich wollte auch 
wegen des ersten ungenauen Puls wissen, ob die nachfolgenden Pulse exakt 
sind...Aber sie sind es: der 2 µS Puls zum Schluss wird auf dem 
Speicheroszi exakt mit 2 µS gemessen. Wenn ich im Callback vor dem 
Stoppen des Interrupts einen Breakpoint setze, sehen die verschiedenen 
Pulse bei konstanter Periode bis auf den ersten PWM Durchlauf stets 
präzise aus.

Kann mir jemand bei diesem Problem helfen? Oder gibt es eine andere 
Möglichkeit, eine Abfolge von Rechtecksignalen bis runter in den 
zweistelligen Mikrosekundenbereich zu erzeugen?

Vielen Dank und liebe Grüße
Hendrik

von Harry L. (mysth)


Lesenswert?


von Hendrik (hendrik2812)


Lesenswert?

Nein, das kannte ich noch nicht. Vielen Dank, ich beschreibe die 
Register ARR und CCR1 jetzt direkt und damit konnte ich immerhin schon 
mal das Problem mit den dynamischen Perioden lösen.

Für meine Problem mit dem ungenauen ersten Puls habe ich leider noch 
keine Lösung finden können. Ich habe zum Testen ein neues Projekt für 
mein Nucleo Board erstellt und nur den Timer11 auf PWM gestellt (Periode 
auf 1999 , Puls auf 1000). Ansonsten alles auf Default gelassen, Clock 
ist dann HSI 16 MHz, also dauern 1000 Pulse 62,5 µS. Dann habe ich 
direkt vor der Main-Loop mit HAL_TIM_PWM_Start() die PWM gestartet.

Ergebnis: der erste Puls dauert 67,5 µS, alle nachfolgenden 62,5 µS. Ich 
meine, ich kann da ja eigentlich nicht so viel falsch gemacht haben, da 
ja fast alles von der IDE erstellt wird und ich lediglich die PWM im 
Blocking Mode gestartet habe. Weiß echt keiner, woran diese erste 
Ungenauigkeit liegen könnte?

Oder mein Speicheroszi spinnt...muss ich mal mit nem externen Trigger 
prüfen, den ich über den 2. Kanal des Oszis etwas eher als die PWM 
auslöse.

von STM32 (Gast)


Lesenswert?

Man kann den Inhalt der PWM-Register auch per DMA füllen lassen. Das 
kann der Timer selbst triggern, z.B. bei jedem n-ten Durchlauf 
(n=1..256).
Damit kann man sehr komplexe Pattern generieren oder auch eine 
3-Phase-Sinus-Spannung erzeugen, wenn man mehrere Compare-Register so 
füttert.

Das braucht etwas Spucke und Geduld, aber danach macht das die HW quasi 
von selbst. Man fragt sich, was man mit der schlafenden 32-Bit-CPU 
anfangen könnte.

von STM32 (Gast)


Lesenswert?

BTW, AN4776 Seite 44ff

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.