Forum: Mikrocontroller und Digitale Elektronik STM32 Timer muss skaliert werden für richtige Messung


von Bert S. (kautschuck)


Lesenswert?

Hi,

Ich brauche einen Timer für die Geschwindigkeitsregelung eines Motors 
auf einem STM32F4 und das ganze klappt auch wunderbar über die ganze 
Bandbreite des Motors (60rpm bis 5000rpm) auf +- 0.1 rpm genau, aber nur 
wenn ich den Timer mit dem Faktor 1.07 multipliziere. Es sind im übrigen 
alle Timer betroffen.

Da der Faktor ziemlich neutral ist (nicht so 1.7348922, sondern nur 
1.07) dachte ich an einen falschen prescaler oder so, aber dem ist nicht 
so:

-uC läuft mit 100Mhz mit einem externen 16Mhz Quarz.
-Timer:
1
/* TIM9 init function */
2
static void MX_TIM9_Init(void)
3
{
4
5
  TIM_OC_InitTypeDef sConfigOC;
6
7
  htim9.Instance = TIM9;
8
  htim9.Init.Prescaler = 9;
9
  htim9.Init.CounterMode = TIM_COUNTERMODE_UP;
10
  htim9.Init.Period = 65535;
11
  htim9.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
12
  if (HAL_TIM_OC_Init(&htim9) != HAL_OK)
13
  {
14
    _Error_Handler(__FILE__, __LINE__);
15
  }
16
17
  sConfigOC.OCMode = TIM_OCMODE_TIMING;
18
  sConfigOC.Pulse = 0;
19
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
20
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
21
  if (HAL_TIM_OC_ConfigChannel(&htim9, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
22
  {
23
    _Error_Handler(__FILE__, __LINE__);
24
  }
25
26
}

-Messung Cyclic Zeit für Geschwindigkeitsmessung:
1
float t;
2
stopTimeMeasureing(&t,HTIM9);
3
startTimeMeasureing(HTIM9);
4
5
void startTimeMeasureing(TIMER_INTERFACES interface) {
6
  switch(interface) {
7
    case HTIM1:
8
    {
9
      break;
10
    }
11
    case HTIM5:
12
    {
13
      break;
14
    }
15
    case HTIM6:
16
    {
17
      tim6->Instance->CNT=0; 
18
      break;
19
    }
20
    case HTIM9:
21
    {
22
      tim9->Instance->CNT=0; 
23
      break;
24
    }
25
    case HTIM11:
26
    {
27
      break;
28
    }
29
30
    default: break;
31
  }
32
}
33
34
void stopTimeMeasureing(float *t,TIMER_INTERFACES interface) {
35
  switch(interface) {
36
    case HTIM1:
37
    {
38
      break;
39
    }
40
    case HTIM5:
41
    {
42
      break;
43
    }
44
    case HTIM6:
45
    {
46
      *t=tim6->Instance->CNT;
47
      *t=*t*100; //us
48
      break;
49
    }
50
    case HTIM9:
51
    {
52
      *t=tim9->Instance->CNT;
53
      current_cycling_time=*t/1e7;
54
55
      break;
56
    }
57
    case HTIM11:
58
    {
59
      break;
60
    }
61
62
    default: break;
63
  }
64
}

Ich kann mir das wirklich nicht erklären und in einer älteren 
Programmversion ging es auch noch ohne Faktor. Jemand eine Idee? Der 
Quarz kann nicht falsch sein, da UART und CO noch Fehlerfrei laufen.

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

Lass doch mal die ganze Motorregelung weg und lass dir in der ISR des 
Timers einen Pin togglen und messe mit dem Oszilloskop, ob die Frequenz 
stimmt.

Bert S. schrieb:
> -Messung Cyclic Zeit für Geschwindigkeitsmessung:

Du fragst den Timer-Stand also manuell ab? Dann kann das Ergebnis dank 
variierender Programmlaufzeit doch beliebig abweichen.

von Bert S. (kautschuck)


Lesenswert?

Dr. Sommer schrieb:
> Du fragst den Timer-Stand also manuell ab? Dann kann das Ergebnis dank
> variierender Programmlaufzeit doch beliebig abweichen.

Ja, ich habe das mal auch noch so probiert:
1
    float t;
2
    startTimeMeasureing(HTIM1);
3
    HAL_Delay(5);
4
    stopTimeMeasureing(&t,HTIM1);
5
    sprintf(data_buf,"t:%d\n",(int)t);
6
    usart_tx_dma(256,data_buf,USB_UART);

Das gibt mir 59832 aus, statt den 50000 die ich bräuchte, auch wenn 
alles andere deaktiviert ist (keine Interrupts). Wie würdest du denn die 
Zeit auslesen, mit dem DMA oder gleich Interrupt?

Dr. Sommer schrieb:
> Lass doch mal die ganze Motorregelung weg und lass dir in der ISR des
> Timers einen Pin togglen und messe mit dem Oszilloskop, ob die Frequenz
> stimmt.

Jo, das mache ich mal.

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

Bert S. schrieb:
> Wie würdest du denn die
> Zeit auslesen, mit dem DMA?

Ich würde den Input Capture / PWM Input Mode des Timers nehmen, damit 
dieser exakt in Hardware die Zeit misst und man lediglich das Ergebnis 
aus dem Register lesen muss. Dazu muss das Encoder-Signal auf einen Pin 
des Timers gelegt werden.

von Bert S. (kautschuck)


Lesenswert?

Dr. Sommer schrieb:
> Ich würde den Input Capture / PWM Input Mode des Timers nehmen, damit
> dieser exakt in Hardware die Zeit misst und man lediglich das Ergebnis
> aus dem Register lesen muss. Dazu muss das Encoder-Signal auf einen Pin
> des Timers gelegt werden.

Ok, das macht natürlich schon mehr Sinn, IC wird sowiso verwendet für 
den Encoder, die Zeitmessung war aber für den Luenberg Observer zur 
Geschwindigkeitsschätzung. Aber trotzdem kann ich mir nicht erklären, 
warum die Zeitmessung so extrem daneben liegt, Interrupts treten nicht 
wirklich viele auf. Ich denke der Fehler muss irgendwo anders liegen, 
zumindest scheint der Timer mit dem Oszi genau.

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

Benutzt du denn die FPU, oder werden deine Floating-Point-Operationen in 
Software berechnet (langsam)? Braucht es unbedingt float?
Wenn du CNT auf 0 setzt, wird der Prescaler-Zähler nicht auf 0 gesetzt. 
Schreibe stattdessen in das EGR, dann wird beides automatisch auf 0 
gesetzt. Timer im laufenden Betrieb zu setzen und abzufragen ist ungenau 
und macht nur Ärger...

von Bert S. (kautschuck)


Lesenswert?

Dr. Sommer schrieb:
> Benutzt du denn die FPU, oder werden deine Floating-Point-Operationen in
> Software berechnet (langsam)?

FPU müsste aktiviert sein:

1
void SystemInit(void)
2
{
3
  /* FPU settings ------------------------------------------------------------*/
4
  #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
5
    SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));  /* set CP10 and CP11 Full Access */
6
  #endif
7
  /* Reset the RCC clock configuration to the default reset state ------------*/
8
  /* Set HSION bit */
9
  RCC->CR |= (uint32_t)0x00000001;
10
11
  /* Reset CFGR register */
12
  RCC->CFGR = 0x00000000;
13
14
  /* Reset HSEON, CSSON and PLLON bits */
15
  RCC->CR &= (uint32_t)0xFEF6FFFF;
16
17
  /* Reset PLLCFGR register */
18
  RCC->PLLCFGR = 0x24003010;
19
20
  /* Reset HSEBYP bit */
21
  RCC->CR &= (uint32_t)0xFFFBFFFF;
22
23
  /* Disable all interrupts */
24
  RCC->CIR = 0x00000000;
25
26
#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
27
  SystemInit_ExtMemCtl(); 
28
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
29
30
  /* Configure the Vector Table location add offset address ------------------*/
31
#ifdef VECT_TAB_SRAM
32
  SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
33
#else
34
  SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
35
#endif
36
}

Dr. Sommer schrieb:
> Wenn du CNT auf 0 setzt, wird der Prescaler-Zähler nicht auf 0 gesetzt.
> Schreibe stattdessen in das EGR, dann wird beides automatisch auf 0
> gesetzt. Timer im laufenden Betrieb zu setzen und abzufragen ist ungenau
> und macht nur Ärger...

Ok danke, ich probiere das mal.

von Dr. Sommer (Gast)


Lesenswert?

Bert S. schrieb:
> FPU müsste aktiviert sein:

Erzeugt der Compiler denn auch Instruktionen für die FPU? Die FPU bringt 
nichts wenn eine Soft-Float-Library aufgerufen wird...

von Bert S. (kautschuck)


Lesenswert?

Dr. Sommer schrieb:
> Erzeugt der Compiler denn auch Instruktionen für die FPU? Die FPU bringt
> nichts wenn eine Soft-Float-Library aufgerufen wird...

Ich verwende Atollic Truestudio, soweit ich das gelesen habe, 
unterstützt das der Compiler.

Die Compiler Optionen sind folgende:
1
-mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -std=gnu11 -D__weak=__attribute__((weak)) -D__packed=__attribute__((__packed__)) -DUSE_HAL_DRIVER -DSTM32F410Cx -I../Inc -I../Drivers/STM32F4xx_HAL_Driver/Inc -I../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32F4xx/Include -I../Drivers/CMSIS/Include -I../Firmware/1_app/inc -I../Firmware/2_hlvl/inc -I../Firmware/3_phal/inc -I../Firmware/4_llvl/inc -I../Firmware/5_global/inc -Os -ffunction-sections -fdata-sections -g -fstack-usage -Wall -specs=nano.specs

-mfloat-abi=hard müsste den FPU unterstützen.

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

Bert S. schrieb:
> -mfloat-abi=hard -mfpu=fpv4-sp-d16

Ja, das sieht gut aus. Schau mal im disassemblierten Code, ob da Dinge 
wie "vmul" oder "vadd" auftauchen, das sind FPU-Anweisungen.

von Hermann K. (r2d2)


Lesenswert?

Bert S. schrieb:
> Ja, ich habe das mal auch noch so probiert:    float t;
>     startTimeMeasureing(HTIM1);
>     HAL_Delay(5);
>     stopTimeMeasureing(&t,HTIM1);
>     sprintf(data_buf,"t:%d\n",(int)t);
>     usart_tx_dma(256,data_buf,USB_UART);
>
> Das gibt mir 59832 aus, statt den 50000 die ich bräuchte, auch wenn
> alles andere deaktiviert ist (keine Interrupts).

Achtung: HAL_Delay() baut auf dem SysTick-IRQ auf, der nur 1x ms 
auslöst. Je nachdem wann du startest kann dein Delay also um 1ms 
abweichen. Die 59832 wären damit im normalen Rahmen.

von Bert S. (kautschuck)


Lesenswert?

Hermann K. schrieb:
> Achtung: HAL_Delay() baut auf dem SysTick-IRQ auf, der nur 1x ms
> auslöst. Je nachdem wann du startest kann dein Delay also um 1ms
> abweichen. Die 59832 wären damit im normalen Rahmen.

Stimmt, da hast du recht, der ist immer um 1ms verschoben, daher passt 
der Timer also und auch das Auslesen. Der Fehler muss also woanders 
liegen.

Dr. Sommer schrieb:
> Ja, das sieht gut aus. Schau mal im disassemblierten Code, ob da Dinge
> wie "vmul" oder "vadd" auftauchen, das sind FPU-Anweisungen.

Ja, das taucht auf im .list file, dann passt das schon.

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

Toggle im Moment der Messung ("CNT"-Zugriff) mal einen Pin und 
oszilloskopiere den gleichzeitig  zum Eingangs-Pin.

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.