STM32 Main loop zu langsam?

Gast #5105216
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
}
Gast #5105220
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.
Gast #5105236
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
Gast #5105256
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
Gast #5105358
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
}
#5105397
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
Gast #5105441
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.
#5105507
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
#5105606
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.
Gast #5106473
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.
Gast #5170022
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)?
(Firma: EleLa - www.elela.de) #5170059
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;

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