Forum: Mikrocontroller und Digitale Elektronik STM32 Main loop zu langsam?


von Julian (Gast)


Lesenswert?

Hallo,
ich bin gerade dabei mit dem STM32CubeMX und SW4STM32 ein Programm für 
einen STM32f446RE zu schreiben.
Da ich das erste mal SW4STM32 und den CubeMX für die STM's nutze habe 
ich relativ wenig Erfahrung damit.
Mein aktuelles Problem ist, das wenn ich in der Mainschleife einen Pin 
Toggeln lasse ohne irgendwelche Delays, erhalte ich lediglich eine 
Frequenz von 6Mhz an dem Pin.
Dafür das ich die vollen 180MHz als SystemTakt eingestellt habe finde 
ich das sehr wenig. Die 180Mhz habe ich auch schon über den MCO Output 
überprüft und die sind auch vorhanden.
Was kann die Hauptschleife noch so verlangsamen? Ich habe bis jetzt nur 
das Grundgerüst vom CubeMX und das Toggeln als Code. Da müsste doch 
eigentlich mehr gehen oder?
1
#include "stm32f4xx_hal.h"
2
3
void SystemClock_Config(void);
4
static void MX_GPIO_Init(void);
5
6
int main(void)
7
{
8
9
  /* USER CODE BEGIN 1 */
10
11
  /* USER CODE END 1 */
12
13
  /* MCU Configuration----------------------------------------------------------*/
14
15
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
16
  HAL_Init();
17
18
  /* Configure the system clock */
19
  SystemClock_Config();
20
21
  /* Initialize all configured peripherals */
22
  MX_GPIO_Init();
23
24
  /* USER CODE BEGIN 2 */
25
26
  /* USER CODE END 2 */
27
  /* Infinite loop */
28
  /* USER CODE BEGIN WHILE */
29
  while (1)
30
  {
31
  /* USER CODE END WHILE */
32
33
    HAL_GPIO_TogglePin(LI_CH1_GPIO_Port,LI_CH1_Pin);
34
35
  /* USER CODE BEGIN 3 */
36
37
  }
38
  /* USER CODE END 3 */
39
40
}
41
42
/** System Clock Configuration
43
*/
44
void SystemClock_Config(void)
45
{
46
47
  RCC_OscInitTypeDef RCC_OscInitStruct;
48
  RCC_ClkInitTypeDef RCC_ClkInitStruct;
49
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;
50
51
  __PWR_CLK_ENABLE();
52
53
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
54
55
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
56
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
57
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
58
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
59
  RCC_OscInitStruct.PLL.PLLM = 4;
60
  RCC_OscInitStruct.PLL.PLLN = 180;
61
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
62
  RCC_OscInitStruct.PLL.PLLQ = 8;
63
  RCC_OscInitStruct.PLL.PLLR = 2;
64
  HAL_RCC_OscConfig(&RCC_OscInitStruct);
65
66
  HAL_PWREx_ActivateOverDrive();
67
68
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
69
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
70
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
71
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
72
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
73
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
74
  HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);
75
76
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SDIO|RCC_PERIPHCLK_CK48;
77
  PeriphClkInitStruct.Clk48ClockSelection = RCC_CK48CLKSOURCE_PLLQ;
78
  PeriphClkInitStruct.SdioClockSelection = RCC_SDIOCLKSOURCE_CK48;
79
  HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
80
81
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
82
83
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
84
85
  /* SysTick_IRQn interrupt configuration */
86
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
87
}
88
void MX_GPIO_Init(void)
89
{
90
91
  GPIO_InitTypeDef GPIO_InitStruct;
92
93
  /* GPIO Ports Clock Enable */
94
  __GPIOH_CLK_ENABLE();
95
  __GPIOC_CLK_ENABLE();
96
  __GPIOA_CLK_ENABLE();
97
  __GPIOB_CLK_ENABLE();
98
  __GPIOD_CLK_ENABLE();
99
100
  /*Configure GPIO pin Output Level */
101
  HAL_GPIO_WritePin(GPIOC, HI_CH3_Pin|LI_CH3_Pin|HI_CH4_Pin|LI_CH4_Pin, GPIO_PIN_RESET);
102
103
  /*Configure GPIO pin Output Level */
104
  HAL_GPIO_WritePin(GPIOA, LCD_CS_Pin|HI_CH1_Pin|LI_CH1_Pin|HI_CH2_Pin 
105
                          |LI_CH2_Pin, GPIO_PIN_RESET);
106
107
  /*Configure GPIO pin Output Level */
108
  HAL_GPIO_WritePin(GPIOB, Touch_CS_Pin|LCD_Reset_Pin|LED_CTRL_Pin, GPIO_PIN_RESET);
109
110
  /*Configure GPIO pins : HI_CH3_Pin LI_CH3_Pin HI_CH4_Pin LI_CH4_Pin */
111
  GPIO_InitStruct.Pin = HI_CH3_Pin|LI_CH3_Pin|HI_CH4_Pin|LI_CH4_Pin;
112
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
113
  GPIO_InitStruct.Pull = GPIO_NOPULL;
114
  GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
115
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
116
117
  /*Configure GPIO pins : LCD_CS_Pin HI_CH1_Pin LI_CH1_Pin HI_CH2_Pin 
118
                           LI_CH2_Pin */
119
  GPIO_InitStruct.Pin = LCD_CS_Pin|HI_CH1_Pin|LI_CH1_Pin|HI_CH2_Pin 
120
                          |LI_CH2_Pin;
121
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
122
  GPIO_InitStruct.Pull = GPIO_NOPULL;
123
  GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
124
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
125
126
  /*Configure GPIO pins : Touch_CS_Pin LCD_Reset_Pin LED_CTRL_Pin */
127
  GPIO_InitStruct.Pin = Touch_CS_Pin|LCD_Reset_Pin|LED_CTRL_Pin;
128
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
129
  GPIO_InitStruct.Pull = GPIO_NOPULL;
130
  GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
131
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
132
133
  /*Configure GPIO pin : LCD_D_C_Pin */
134
  GPIO_InitStruct.Pin = LCD_D_C_Pin;
135
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
136
  GPIO_InitStruct.Pull = GPIO_NOPULL;
137
  HAL_GPIO_Init(LCD_D_C_GPIO_Port, &GPIO_InitStruct);
138
139
  /*Configure GPIO pin : IRQ_Touch_Pin */
140
  GPIO_InitStruct.Pin = IRQ_Touch_Pin;
141
//  GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
142
  GPIO_InitStruct.Pull = GPIO_NOPULL;
143
  HAL_GPIO_Init(IRQ_Touch_GPIO_Port, &GPIO_InitStruct);
144
145
}

von Irgendwer (Gast)


Lesenswert?

1
while (1)
2
{
3
  HAL_GPIO_TogglePin(LI_CH1_GPIO_Port,LI_CH1_Pin);
4
}

Wenn man zum schnöden ändern von einem Pin sich den Luxus eines Aufrufs 
einer aufgeblasenen Funktion gönnt darf man sich über nichts wundern.

von 458iu7888766687 (Gast)


Lesenswert?

Irgendwer schrieb:
> Wenn man zum schnöden ändern von einem Pin sich den Luxus eines Aufrufs
> einer aufgeblasenen Funktion gönnt darf man sich über nichts wundern.

HAL oder die LL Lib sind ja nicht total kacke ...
Es gibt genug grunde das zeug einzusetzen

Aber nur um den PIN so schnell wie möglich wackeln zu lassen ... ist das 
auch der der falsche ansatz
selbst der Weg mit leerer Main loop ist der total falsche !!

wenn man zB eine PWM nutzt oder andere HW features ... ist der einsatz 
der HAL oder LL Lib sicher undramatisch

der vorteil wäre:
man klickt sich das im Cube zusammen und hat 10 sek später den Ausgang 
am wackeln...

oder man liest sich durchs manual und sucht sich entsprechende Register 
und bits raus

von stm32 (Gast)


Lesenswert?

Der peripheral Bus muss langsamer laufen als 180 MHz. Zudem noch der 
erwähnte mögliche overhead.

von A. S. (Gast)


Lesenswert?

6MHz, also 12 Mio Durchläufe, also 15 Quarz-takte für
 Zustand abfragen,
togglen,
schreiben
Sprung.

Das ist ok. Verbesserungsmöglichkeiten: togglen ausrollen, also High, 
Low.
Noch besser: direkt, ohne hal

von Mathias _. (mathias1988)


Lesenswert?

Wie meine Vorredner schon geschrieben haben, ist 
Systemclock!=Peripherieclock. Die HAL-Bibliothek ist zwar praktisch, 
aber von der Geschwindigkeit her bist du wesentlich schneller, wenn du 
direkte Registerzugriffe machst. Viel Glück!

von Axel R. (Gast)


Lesenswert?

Ich kenn mich nich aus, aber:
>GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
"LOW" impliziert "laangsaam". Wird aber mit dem Problem hier nichts zu 
tun haben, oder?

StromTuner

von Mampf F. (mampf) Benutzerseite


Lesenswert?

Es ist mir fast unangenehm so etwas zu fragen, aber 
vollständigkeitshalber muss ich es machen: Hast du deinen Code mit -O3 
oder -Os kompiliert?

von Mathias _. (mathias1988)


Lesenswert?

Trotz der Einstellung "GPIO_SPEED_LOW" sollte der Pin periodisch 
schneller zu toggeln sein. Aus eigener Erfahrung weis ich, dass die 
HAL-Lib einfach sehr viele Schritte macht, bis das richtige 
GPIO-Register gesetzt wird.

: Bearbeitet durch User
von Irgendein anderer (Gast)


Lesenswert?

Irgendwer schrieb:
> while (1)
> {
>   HAL_GPIO_TogglePin(LI_CH1_GPIO_Port,LI_CH1_Pin);
> }
>
> Wenn man zum schnöden ändern von einem Pin sich den Luxus eines Aufrufs
> einer aufgeblasenen Funktion gönnt darf man sich über nichts wundern.

Hast du schon mal in diese achso aufgeblasene Funktion reingeschaut? 
Wenn nicht hier bitteschön:
1
void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
2
{
3
  /* Check the parameters */
4
  assert_param(IS_GPIO_PIN(GPIO_Pin));
5
6
  GPIOx->ODR ^= GPIO_Pin;
7
}

von Irgendwer (Gast)


Lesenswert?

Irgendein anderer schrieb:
> /* Check the parameters */
>   assert_param(IS_GPIO_PIN(GPIO_Pin));

Und da hast du dir schon gleich die nächsten Funktionsaufrufe 
eingefangen:-)

von Stefan K. (stefan64)


Lesenswert?

Axel R. schrieb:
> Ich kenn mich nich aus, aber:
>>GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
> "LOW" impliziert "laangsaam". Wird aber mit dem Problem hier nichts zu
> tun haben, oder?

Nein.
Durch diese Einstellung wird die Treiberstärke der Ausgangsstufe 
eingestellt. Damit ist die physikalische Schaltgeschwindigkeit geringer 
und es gibt weniger EMV-Störungen. Am Oszi wird die Rechteck-Kurve etwas 
verschliffener aussehen.
Mit der Geschwindigkeit, die das Programm zum Schalten des Pins 
benötigt, hat das aber nichts zu tun.

Natürlich braucht das Pin-toggeln über die HAL deutlich länger als ein 
optimierter Registerzugriff. Auf der anderen Seite: wer wirklich mit 
High-Speed Pins schalten will, der hat beim STM32 wesentlich bessere 
Möglichkeiten zur Verfügung.

Viele Grüße, Stefan

von Johannes S. (Gast)


Lesenswert?

Irgendwer schrieb:
> Und da hast du dir schon gleich die nächsten Funktionsaufrufe
> eingefangen:-)

was ist falsch daran ein generisches Argument auf Gültigkeit zu prüfen? 
Wenn man alles dem Performancegott opfern möchte und lieber lange Fehler 
sucht kann man solche Abfangjäger ja weglassen.
Das assert wird auch nur wirksam wenn man es möchte und das Symbol 
USE_FULL_ASSERT setzt, sonst wird das wegoptimiert.

von Mampf F. (mampf) Benutzerseite


Lesenswert?

Irgendwer schrieb:
> Irgendein anderer schrieb:
>> /* Check the parameters */
>>   assert_param(IS_GPIO_PIN(GPIO_Pin));
>
> Und da hast du dir schon gleich die nächsten Funktionsaufrufe
> eingefangen:-)

Dachte, asserts sind nur im Debug-Modus aktiv ...^^

von Nils P. (ert)


Lesenswert?

Mathias _. schrieb:
> Wie meine Vorredner schon geschrieben haben, ist
> Systemclock!=Peripherieclock. Die HAL-Bibliothek ist zwar praktisch,
> aber von der Geschwindigkeit her bist du wesentlich schneller, wenn du
> direkte Registerzugriffe machst. Viel Glück!

Habe eben ins Datenblatt geschaut und dort steht der GPIO hängt an dem 
AHB1 mit 180MHz...

Ich hatte irgendwann mal gelesen, dass der IO Pin  beim STM mehrere 
Takte braucht um einen Zustand zu setzen (ab einer bestimmten Frequenz), 
finde ich aber grade nicht... Hat hier einer mehr Details dazu?

G Ert

von Nico W. (nico_w)


Lesenswert?

Das Toggle hat ja minimum schon 3 Takte. Read, modify, write. Dann ggf. 
vorher noch Push. Eigentliches Register laden. Nachher Pop.

Einfach mal ins Assembly gucken.

Und vielleicht läuft die Prefetch Engine nicht so gut. Kannst ja 
testweise den Code mal in den RAM schupsen und gucken.

Da ist aber noch Luft nach oben. Auf einem F411 mit 96MHz hatte ich nen 
toggle ohne unroll mit etwas über 18MHz am laufen ohne HAL.

: Bearbeitet durch User
von bla (Gast)


Lesenswert?

Interessantes Thema.

Hier die Assembly im Compiler Explorer:
https://godbolt.org/g/3yhzYd

Bei -O3 sieht das so aus:
1
.L3:
2
ldr     r3, [r2, #20]   | 3 Takte
3
eor     r3, r3, #1      | 1 Takt
4
str     r3, [r2, #20]   | 3 Takte
5
b       .L3             | 1 + P Takte

Die ARM ISA sagt zu P folgendes: /The number of cycles required for a 
pipeline refill. This ranges from 1 to 3 depending on the alignment and 
width of the target instruction, and whether the processor manages to 
speculate the address early./ 
(http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.100166_0001_00_en/ric1417449098079.html). 
LDR und STR können unter günstigen Umständen (pipelining) auch schneller 
sein, das ist mir nicht so klar wann das ist.

b kann also 2-4 Takte brauchen, ich nehme mal den Worst-Case von b=3 an. 
In Summe sind das dann 11 Takte pro Toggle, x2 für eine komplette 
Periode. 180/22 = 8MHz. Du bist also nah an der Grenze des technisch 
machbaren.

Das doofe am Toggeln ist ja dass du den Pin lesen musst, obwohl in 
deiner Funktion das gar nicht nötig ist. Schneller sollte es gehen wenn 
du einfach hard-coded deine Schleife durchfährst, dann sind die stores 
gepipelined (https://www.arm.com/files/pdf/CortexM3_Uni_Intro.pdf, Seite 
31)
https://godbolt.org/g/UHEHHV
1
.L4:
2
str     r1, [r3, #24]   | 1 Takt (+2 Latenz)
3
str     r2, [r3, #24]   | 1 Takt
4
b       .L4             | 1 + P Takte

Damit dauert deine Periode nur noch 6 Takte (= 30MHz), auf Kosten eines 
asymmetrischen Rechtecks.
1
.L4:
2
str     r1, [r3, #24]   | 1 Takt
3
nop                     | 1 Takt
4
nop                     | 1 Takt
5
str     r2, [r3, #24]   | 1 Takt
6
b       .L4             | 1 + P Takte

Das ist ein symmetrisches Rechteck mit 22,5MHz (=180MHz/8). Wenn es noch 
schneller sein sollte, würde ich mir die Peripherie anschauen, 
vielleicht kann man irgendwelche Hochgeschwindigkeits-Peripherie mit 
180MHz Daten ausspucken lassen.

von Ulf (Gast)


Lesenswert?

Ich hatte gerade die gleiche Frage wie OP. Allerdings sieht mein 
Hauptprogramm so aus:
1
uint32_t i = 0;
2
while (1) {
3
  if (i > 2000000L) {
4
    HAL_GPIO_TogglePin(LED2_GPIO_PORT, LED2_PIN);
5
    i = 0;
6
  }
7
  ++i;
8
}

Die LED blink im 1-Sekunden-Takt, d.h. die Schleife läuft mit 2 "MHz", 
entsprechend der Durchläufe. Der Takt ist allerdings 64 MHz, d.h. wo 
werden hier 32 Zyklen pro Schleife verbraten? (Die HAL o.ä. spielt hier 
keine Rolle.)

Wie kann ein STM32 so langsam sein? Ich wollte ihn eigentlich als 
Bus-Treiber (1-3 MHz) einsetzen. Sind die anderen Hersteller schneller 
(ich vermute nicht)? Gibt es schnellere Alternativen (außer FPGA)?

von STM Apprentice (Gast)


Lesenswert?

Ulf schrieb:
> Der Takt ist allerdings 64 MHz,

Dann ist das wohl kein STM32F4xx und du vergleichst
Äpfel mit Birnen ....?

von Ulf (Gast)


Lesenswert?

Nein, das ist ein STM32F1, aber das Prinzip dürfe wohl gleich sein.

von Markus M. (Firma: EleLa - www.elela.de) (mmvisual)


Lesenswert?

Schau mal ob das schneller geht:
1
while(1) {
2
  LI_CH1_GPIO_Port->BS = LI_CH1_Pin;
3
  LI_CH1_GPIO_Port->BR = LI_CH1_Pin;
4
}

Kompiliert mit -os.


PS ich habe die Struktur bei mir so angepasst, damit habe ich direkten 
Zugriff auf die Setz/Reset Register (BSRR):
1
typedef struct
2
{
3
  __IO uint32_t MODER;    /*!< GPIO port mode register,               Address offset: 0x00      */
4
  __IO uint32_t OTYPER;   /*!< GPIO port output type register,        Address offset: 0x04      */
5
  __IO uint32_t OSPEEDR;  /*!< GPIO port output speed register,       Address offset: 0x08      */
6
  __IO uint32_t PUPDR;    /*!< GPIO port pull-up/pull-down register,  Address offset: 0x0C      */
7
  __IO uint32_t IDR;      /*!< GPIO port input data register,         Address offset: 0x10      */
8
  __IO uint32_t ODR;      /*!< GPIO port output data register,        Address offset: 0x14      */
9
  union
10
  {
11
    __IO uint32_t BSRR; /*!< GPIO port bit set/reset register,      Address offset: 0x18      */
12
    struct
13
    {
14
      __IO uint16_t BS; /*!< GPIO port bit set/reset low register,  Address offset: 0x18    */
15
      __IO uint16_t BR; /*!< GPIO port bit set/reset high register, Address offset: 0x1A    */
16
    };
17
  };
18
  __IO uint32_t LCKR;     /*!< GPIO port configuration lock register, Address offset: 0x1C      */
19
  __IO uint32_t AFR[2];   /*!< GPIO alternate function registers,     Address offset: 0x20-0x24 */
20
} GPIO_TypeDef;

von STM Apprentice (Gast)


Lesenswert?

Markus M. schrieb:
> Schau mal ob das schneller geht:

Gut gemeint, aber ....

Für wen war dein Vorschlag?

von Ulf (Gast)


Lesenswert?

Ja, bei mir wird die LED nur einmal pro 2 Mio. Durchläufen angesprochen, 
d.h. deren Performance ist vernachlässigbar.

Aber -Os werde ich mal ausprobieren.

von Ulf (Gast)


Lesenswert?

Interessant ... -O3 und -Os haben einen Faktor 4x herausgeschlagen. Ob 
-O3 oder -Os besser ist, kann ich mit dem bloßen Auge nicht erkennen.

von STM Apprentice (Gast)


Lesenswert?

Ulf schrieb:
> Ob
> -O3 oder -Os besser ist, kann ich mit dem bloßen Auge nicht erkennen.

Kann ja auch zufällig das gleiche Optimierungs-Ergebnis herauskommen.

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.