Forum: Mikrocontroller und Digitale Elektronik STM32F0 delay in mikrosekunden


von Gerald R. (visitor)


Lesenswert?

Hallo!

Ich habe vor mit meinem STM32F0 einen Servo zu steuern.
Leider scheitere ich bereits am Anfang.

Ich benutze CooCox.

Bei dem Treiber für das LC-Display war ein Delay dabei, welches ca. 1ms 
bewirkt.
1
    inline void DelayMs(uint32_t nTime)
2
  {
3
    volatile uint32_t n = nTime * (SystemCoreClock -1) / 12000;
4
    while(n > 0) n--;
5
  }


Mit diesem DelayMs reagiert der Servo, und ich kann 2 Positionen 
anfahren wenn ich für den ersten Wert 1 oder 2 eintrage.
1 ist halb links, 2 ist halb rechts.
1
          else if(Funktion == 2) // Servotest
2
                {
3
                  LCD_Goto(1,1);
4
                  LCD_Puts("Funktion: Servo");
5
                  LCD_Goto(1,2);
6
                  LCD_Puts("Servo1:");
7
                    //PWM_Output();
8
                    GPIO_InitTypeDef InitGpio;
9
                    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
10
                    InitGpio.GPIO_Pin = (GPIO_Pin_8);
11
                    InitGpio.GPIO_Mode = GPIO_Mode_OUT;
12
                    InitGpio.GPIO_Speed = GPIO_Speed_Level_3;
13
                    InitGpio.GPIO_OType = GPIO_OType_PP;
14
                    InitGpio.GPIO_PuPd = GPIO_PuPd_NOPULL;
15
                    GPIO_Init(GPIOA, &InitGpio);
16
17
                    for (Servotime = 0; Servotime < 200; ++Servotime) {
18
                      GPIO_SetBits(GPIOA, (GPIO_Pin_8));
19
                      DelayMs(2);
20
                      GPIO_ResetBits(GPIOA, (GPIO_Pin_8));
21
                      DelayMs(20);
22
              }
23
                }

Um die Positionen genauer zu bestimmen brauche ich einen kleineren Wert 
für das Delay.
Meine naive Denkweise war hier den Teiler zu erhöhen, und damit dann 
einen Timer mit µs oder zumindest 100µs bereitzustellen.


Jedoch hier mein Problem:
Wenn ich für Delay einen Wert mit 3 Stellen angebe scheint das keine 
Änderung mehr zu bewirken.

Klartext:
DelayMs(10) ergibt das gleiche Verhalten wie DelayMs(100) oder 
DelayMs(1000).


Verständnis Frage:
Der Code für das DelayMs ist nur eine Rechnung, die einen Wert ergibt 
von dem dann bis 0 gezählt wird.
Richtig?

Bei 48MHz (48000000) ergibt die Rechnung 4000*den Wert von DelayMs().


Kann mir bitte jemand anhand eines Beispieles erklären wie man ein Delay 
richtig definiert?

von Uwe B. (derexponent)


Lesenswert?

Gerald R. schrieb:
>
> Verständnis Frage:
> Der Code für das DelayMs ist nur eine Rechnung, die einen Wert ergibt
> von dem dann bis 0 gezählt wird.
> Richtig?
>
> Bei 48MHz (48000000) ergibt die Rechnung 4000*den Wert von DelayMs().
>


deine Annahme ist richtig bei dem Aufruf
DelayMs(1);

wird die Variable "n" von 4000 bis 0 runtergezählt
und das dauert bei einem 48MHz Clock ca. 1ms

ABER : du gehst die ganze Sache falsch an !

kein Mensch programmiert ein Signal für einen Servo mit :
"HI, Pause(x), LO, Pause(y)" innerhalb einer For-Schleife

1. wird die CPU während der kompletten Zeit blockiert

2. bekommst du so nicht (oder nur schwer) die notwendige Auflösung
und Genauigkeit hin, ohne nach jeder Änderung deine Pausen neu zu 
berechnen

3. ist das Verhalten vom Servo undefiniert, wenn deine "Servotime" 
abgelaufen ist und z.B. die Anzeige wieder aktuallisiert wird

für so einen Zweck (Servo) gibt es in der CPU extra einen (bzw mehrere) 
Hardware-Timer die man einmal einstellen muss und die laufen dann
unabhängig von der CPU und steuern deinen Servo mit dem richtigen Signal 
an
also z.B. 1,5ms Impuls und 18,5ms Pause

ließ mal bei den Beispielen von ST (und in der Doku) unter "Timer" bzw 
"PWM"

hier im Forum gibt es auch zich Beispiele wie man sowas realisiert.

falls du Probleme bei der Umsetzung hast, schreib hier nochmal

Gruss Uwe

von Gerald R. (visitor)


Lesenswert?

@ mod
Danke fürs verschieben und sorry fürs posten im falschen Unterforum!

Hallo Uwe!

Danke für die Antwort.
Wie du vermutlich erkannt hast habe ich programmieren bzw. C++ nie 
gelernt und stehe da im Selbststudium total am Anfang.

Uwe B. schrieb:
> deine Annahme ist richtig bei dem Aufruf
> DelayMs(1);
>
> wird die Variable "n" von 4000 bis 0 runtergezählt
> und das dauert bei einem 48MHz Clock ca. 1ms

Wie erklärt sich dass das delay nicht mehr länger wird wenn ich zb. von 
100 auf 1000 erhöhe?

von 40000000 runterzählen muss doch länger dauern als von 400000!?



Ich habe schon einiges Gelesen in der 900 Seiten Doku von STM.
Jedoch ist diese für einen Anfänger oder zumindest für mich doch eher 
nur da um herauszufinden was der Controller alles kann.
Wie man das dann anwendet oder Benutzt muss man entweder wissen oder 
durch sehr langes Probieren und suchen in Foren und example files 
herausfinden.
Fast alle Beispiele die ich finde sind für STM32F4 und dadurch nur durch 
abändern für das F0 zu gebrauchen.

Mit PWM habe ich bereits experimentiert, aber auch hier hatte ich das 
Problem dass ich die links - rechts Grenze nicht erreicht habe.

Werde es aber nochmal mit mehr Geduld angehen.

Ob Erfolg oder nicht, ich werde mich melden.

Danke!

von Uwe B. (Firma: TU Darmstadt) (uwebonnes)


Lesenswert?

Gerald R. schrieb:
> volatile uint32_t n = nTime * (SystemCoreClock -1) / 12000;

Gerald R. schrieb:
> Wie erklärt sich dass das delay nicht mehr länger wird wenn ich zb. von
> 100 auf 1000 erhöhe?

Falls der Compiler den Ausdruck oben als
(nTime * (SystemCoreClock -1)) / 12000;
berechnet, dann gibt es schnell einen Ueberlauf. Probier mal, ob nicht
nTime * ((SystemCoreClock -1) / 12000);
sinnvollere Ergebnisse gibt.

Aber Timer waeren ein besserer Ansatz!

Tschuess

von Gerald R. (visitor)


Lesenswert?

Uwe Bonnes schrieb:
> nTime * ((SystemCoreClock -1) / 12000);


Hallo Uwe!

Genau so ist es!
An so etwas hätte ich nie gedacht.
Habs gerade erfolgreich getestet.

Danke!

von Gerald R. (visitor)


Lesenswert?

Hallo!

Habe jetzt das mit dem PWM Signal und Timer hinbekommen.
Ich kann jetzt mit pulse von 120 bis 30 von links nach rechts.

Hätte aber noch eine Frage:
Wenn ich in der main.c PWM_Output(); aufrufe, blockiert dies meine CPU.
Ich möchte dann ja live den Pulse ändern.
Also ist der Eigentliche Sinn der Benutzung des Timers jetzt mal nicht 
vorhanden.
1
       else if(Funktion == 2) // Servotest
2
             {
3
              LCD_Clear();
4
              LCD_Goto(1,1);
5
              LCD_Puts("Funktion: Servo");
6
              LCD_Goto(1,2);
7
              LCD_Puts("PWM Test");
8
              PWM_Output();
9
             }


Habe die Vorlage von STM benutzt und nur Werte geändert.
Die Teiler sind jetzt noch nicht optimal, aber 90 Schritte für ca. 120° 
sind vorerst mehr als genug.

Ich benutze momentan nur Channel1
Hier meine PWM_Output.c.:
1
#include "stm32f0xx.h"
2
#include "stm32f0xx_rcc.h"
3
#include "stm32f0xx_gpio.h"
4
#include "stm32f0xx_tim.h"
5
6
TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
7
TIM_OCInitTypeDef  TIM_OCInitStructure;
8
uint16_t TimerPeriod = 0;
9
uint16_t Channel1Pulse = 0, Channel2Pulse = 0, Channel3Pulse = 0, Channel4Pulse = 0;
10
11
void TIM_Config(void);
12
13
void PWM_Output(void)
14
{
15
  TIM_Config();
16
17
  TimerPeriod = (SystemCoreClock / 17570 ) - 1;
18
  /* Compute CCR1 value to generate a duty cycle at 50% for channel 1 */
19
  Channel1Pulse = (uint16_t) (((uint32_t) 30 * (TimerPeriod - 1)) / 1000); //120=links 30=rechts
20
  /* Compute CCR2 value to generate a duty cycle at 37.5%  for channel 2 */
21
  Channel2Pulse = (uint16_t) (((uint32_t) 375 * (TimerPeriod - 1)) / 1000);
22
  /* Compute CCR3 value to generate a duty cycle at 25%  for channel 3 */
23
  Channel3Pulse = (uint16_t) (((uint32_t) 25 * (TimerPeriod - 1)) / 100);
24
  /* Compute CCR4 value to generate a duty cycle at 12.5%  for channel 4 */
25
  Channel4Pulse = (uint16_t) (((uint32_t) 125 * (TimerPeriod- 1)) / 1000);
26
27
  /* TIM1 clock enable */
28
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 , ENABLE);
29
30
  /* Time Base configuration */
31
  TIM_TimeBaseStructure.TIM_Prescaler = 351;
32
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Down;
33
  TIM_TimeBaseStructure.TIM_Period = TimerPeriod;
34
  TIM_TimeBaseStructure.TIM_ClockDivision = 0;
35
  TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
36
37
  TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
38
39
  /* Channel 1, 2, 3 and 4 Configuration in PWM mode */
40
  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
41
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
42
  //TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
43
  //TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
44
  //TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
45
  //TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
46
  //TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;
47
48
  TIM_OCInitStructure.TIM_Pulse = Channel1Pulse;
49
  TIM_OC1Init(TIM1, &TIM_OCInitStructure);
50
51
  TIM_OCInitStructure.TIM_Pulse = Channel2Pulse;
52
  TIM_OC2Init(TIM1, &TIM_OCInitStructure);
53
54
  TIM_OCInitStructure.TIM_Pulse = Channel3Pulse;
55
  TIM_OC3Init(TIM1, &TIM_OCInitStructure);
56
57
  TIM_OCInitStructure.TIM_Pulse = Channel4Pulse;
58
  TIM_OC4Init(TIM1, &TIM_OCInitStructure);
59
60
  /* TIM1 counter enable */
61
  TIM_Cmd(TIM1, ENABLE);
62
63
  /* TIM1 Main Output Enable */
64
  TIM_CtrlPWMOutputs(TIM1, ENABLE);
65
66
  /* Infinite loop */
67
  while (1)
68
  {}
69
}
70
71
/**
72
  * @brief  Configure the TIM1 Pins.
73
  * @param  None
74
  * @retval None
75
  */
76
void TIM_Config(void)
77
{
78
  GPIO_InitTypeDef GPIO_InitStructure;
79
80
  /* GPIOA Clocks enable */
81
  RCC_AHBPeriphClockCmd( RCC_AHBPeriph_GPIOA, ENABLE);
82
83
  /* GPIOA Configuration: Channel 1, 2, 3 and 4 as alternate function push-pull */
84
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11;
85
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
86
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
87
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
88
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
89
  GPIO_Init(GPIOA, &GPIO_InitStructure);
90
91
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_2);
92
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_2);
93
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_2);
94
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource11, GPIO_AF_2);
95
96
}

von Uwe B. (derexponent)


Lesenswert?

Gerald R. schrieb:
> Habe jetzt das mit dem PWM Signal und Timer hinbekommen.

sehr gut

> Wenn ich in der main.c PWM_Output(); aufrufe, blockiert dies meine CPU.

das liegt an der "while(1)" innerhalb der Funktion,
die ist natürlich Quatsch an der Stelle

der Timer muss nur einmal Kofiguriert werden,
(mit einer default Einstellung z.B. 1,5ms Impuls) danach kann
dein Programm normal weiterarbeiten
(die Funktion "PWM_Output" darf also nur einmal aufgerufen werden
am besten nach dem start der CPU)

wenn du dann den Wert vom PWM-Signal ändern willst,
(während der Programmlaufzeit)
schreib einfach einen andern Compare-Wert in das
Compare Register für CH1

z.B. so :
1
void setPWM(uint16_t value) {
2
  if(value<TimerPeriod) {
3
    TIM1->CCR1 = value;
4
  }
5
}

ich würde auch andere Timer-Settings benutzen
(damit eine höhere Auflösung möglich wird
und du direkt Impulswerte in us angeben kannst)

hier ein Vorschlag :
1
// Einstellung vom PWM auf 50Hz und einer Auflöung von 1us
2
// (Frq-Timer = 48MHz)
3
TimerPeriod = 19999;
4
TIM_TimeBaseStructure.TIM_Prescaler = 47;

damit kannst du dann eine Impulsbreite von z.B.
1,5ms = 1500us so einstellen :
1
setPWM(1500);

ohne gross umrechnen zu müssen

Servo links = 2000 us
Servo mitte = 1500 us
Serve rechts = 1000 us

Gruss Uwe

von Gerald R. (visitor)


Lesenswert?

Wiedermal vielen Dank Uwe!

Es scheint alles so einfach und logisch wenn du das schreibst ;)

Funktioniert alles wunderbar!
Hätte nicht damit gerechnet hier gleich funktionierenden code zu 
bekommen.
Aber es vereinfacht die Sache ungemein und wieder ein kleines 
Erfolgserlebnis.

Jetzt möchte ich noch 2 Sachen probieren.

1. Der Servo soll langsam von links nach rechts fahren.
Das ist ja nur eine Rechnung, dazu muss ich noch etwas lesen und suchen.

2. Der Servo soll über ein Potentiometer gesteuert werden.
Ich bereits einen ADC Wandler der mir einen Wert zwischen o und 4000 
ausgibt. Sollte also machbar sein ;)

Wenn sich jemand genötigt fühlt mir für eine der Beiden Sachen einen 
Lösungsvorschlag bzw. eine Idee mitzuteilen, dann nur zu.

Wenn ich es nicht schaffe melde ich mich ohnehin :D.

LG
Gerald

von Gerald R. (visitor)


Lesenswert?

Also den Servo mit Potentiometer steuern war ja mal leicht.

Ich hole mir aus der ADC_LowPower den Wert
1
extern uint16_t ADC1ConvertedVoltage;

und setze ihn ein.
1
          if(Funktion == 1) // ADC Wandler
2
                {
3
                  ADC_LowPower();
4
                  LCD_Goto(1,1);
5
                  LCD_Puts("Servo PWM/POTI");
6
                  LCD_Goto(1,2);
7
                  LCD_PutUnsignedInt(ADC1ConvertedVoltage);
8
                  setPWM(ADC1ConvertedVoltage);

Der Wert geht von 0 - 3000
Könnte den Wert zwar noch ändern, aber so kann ich austesten wie weit 
ein Servo bzw. dessen Elektronik das Signal mitmacht.

von Gerald R. (visitor)


Lesenswert?

Die Automatische links rechts Bewegung habe ich so gelöst:
1
          else if(Funktion == 5)
2
                {
3
                  LCD_Goto(1,1);
4
                  LCD_Puts("Servo Funktion 5");
5
                  LCD_Goto(1,2);
6
                  LCD_Puts("Automatik");
7
                  setPWMcounter = 800;
8
                  if (setPWMcounter == 800)
9
                    {
10
                    //rechts nach links
11
                      while (setPWMcounter < 2200)
12
                        {
13
                        setPWM(setPWMcounter);
14
                        ++setPWMcounter;
15
                        DelayMs(1);
16
                        }
17
                    }
18
                  if (setPWMcounter == 2200)
19
                    {
20
                    //links nach rechts
21
                      while (setPWMcounter > 800)
22
                        {
23
                        setPWM(setPWMcounter);
24
                        --setPWMcounter;
25
                        DelayMs(1);
26
                      }
27
                  }
28
                 }

Gibt es diesbezüglich Einwände oder Verbesserungsvorschläge?

von Uwe B. (derexponent)


Lesenswert?

wie wärs damit :
1
else if(Funktion == 5)
2
{
3
  LCD_Goto(1,1);
4
  LCD_Puts("Servo Funktion 5");
5
  LCD_Goto(1,2);
6
  LCD_Puts("Automatik");
7
8
  uint16_t speed=10;
9
10
  //rechts nach links
11
  for(setPWMcounter=800;setPWMcounter<2200;setPWMcounter+=speed)
12
  {
13
    setPWM(setPWMcounter);
14
    DelayMs(1);
15
  }
16
17
  //links nach rechts
18
  for(setPWMcounter=2200;setPWMcounter>800;setPWMcounter-=speed)
19
  {
20
    setPWM(setPWMcounter);
21
    DelayMs(1);
22
  }
23
}

von Gerald R. (visitor)


Lesenswert?

Sehr schön Uwe, vielen Dank für das Beispiel.
Habe es noch etwas abgewandelt.
Das Delay kann so während der for-Schleife durch das Potentiometer 
geändert werden.

Auch für den Text am Display habe ich jetzt einen Counter eingerichtet, 
denn das Schreiben nach Ablauf der for-Schleifen hat eine kleine 
Unterbrechung der kontinuierlichen links-rechts Bewegung verursacht.

Sieht jetzt so aus:
1
          else if(Funktion == 5)
2
                {
3
                  if (LCD_Count == 5)
4
                  {
5
                  LCD_Goto(1,1);
6
                  LCD_Puts("Servo Funktion 5");
7
                  LCD_Goto(1,2);
8
                  LCD_Puts("Automatik POTI");
9
                  LCD_Count++;
10
                  }
11
                  uint16_t speed=5;
12
                  setPWMcounter = 2200;
13
                  //links nach rechts
14
                  for(setPWMcounter=2200;setPWMcounter>800;setPWMcounter-=speed)
15
                  {
16
                    ADC_LowPower();
17
                    setPWM(setPWMcounter);
18
                    DelayUs(ADC1ConvertedVoltage);
19
                  }
20
                  //rechts nach links
21
                  for(setPWMcounter=800;setPWMcounter<2200;setPWMcounter+=speed)
22
                  {
23
                    ADC_LowPower();
24
                    setPWM(setPWMcounter);
25
                    DelayUs(ADC1ConvertedVoltage);
26
                  }
27
                 }

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.