Forum: Mikrocontroller und Digitale Elektronik STM32F10x über PWM-Input-Mode DCF77-Signal hardwaremäßig einlesen


von Florian K. (f-kae)


Lesenswert?

Hallo zusammen,

kann ich an einem STM32F100VBT6B über den PWM-Input-Mode ein 
DCF77-Signal einlesen?

Der Prozessor läuft mit 24MHz.
Das DCF-Signal kommt an Pin D13 an, also TIM4-(Channel-2).

Die beiden Counter für den PWM-Input-Mode sind 16-Bit-Register.
Das heißt sie laufen bis maximal 65535. Das heißt nach ca 2,7ms 
(65535/24M) ist der Counter übergelaufen. Ich brauche für das DCF-Signal 
allerdings 1000ms zum zählen.

Kann ich einen Prescaler hierfür einsetzen?
Der Prescaler aus der Struktur "TIM_ICInitStructure"
1
  TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
funktioniert leider nicht wie benötigt. Da wird auf Wunsch lediglich 
jede, jede 2, jede 4 oder jede 8 Flanke ausgewertet.

Meine Initialisierung sieht folgendermaßen aus und funktioniert auch 
grundsätzlich, nur sind die Werte in den Countern halt leider quatsch. 
Es stehen nahezu wahllos Werte drin. Wobei ich das Verhalten auch nicht 
logisch finde, da eigentlich doch jedesmal 65535 in den beiden Countern 
stehen müsste?! Oder laufen die über und fangen dann von vorne an zu 
zählen?
1
void DCF77_init_PWMInput(void)
2
{
3
  NVIC_InitTypeDef NVIC_InitStructure;
4
  TIM_ICInitTypeDef  TIM_ICInitStructure;
5
  
6
  GPIO_PinRemapConfig(GPIO_Remap_TIM4, ENABLE);
7
  
8
/* TIM3 configuration: PWM Input mode ------------------------
9
     The external signal is connected to TIM4 CH2 pin (PD.13), 
10
     The Rising edge is used as active edge,
11
     The TIM3 CCR2 is used to compute the frequency value 
12
     The TIM3 CCR1 is used to compute the duty cycle value
13
  ------------------------------------------------------------ */
14
15
  TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
16
  TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
17
  TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
18
  TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
19
  TIM_ICInitStructure.TIM_ICFilter = 0x0;
20
21
  TIM_PWMIConfig(TIM4, &TIM_ICInitStructure);
22
23
  /* Select the TIM4 Input Trigger: TI2FP2 */
24
  TIM_SelectInputTrigger(TIM4, TIM_TS_TI2FP2);
25
26
  /* Select the slave Mode: Reset Mode */
27
  TIM_SelectSlaveMode(TIM4, TIM_SlaveMode_Reset);
28
29
  /* Enable the Master/Slave Mode */
30
  TIM_SelectMasterSlaveMode(TIM4, TIM_MasterSlaveMode_Enable);
31
32
  /* TIM enable counter */
33
  TIM_Cmd(TIM4, ENABLE);
34
35
  /* Enable the CC2 Interrupt Request */
36
  TIM_ITConfig(TIM4, TIM_IT_CC2, ENABLE);
37
38
  /* Enable the TIM4 global Interrupt */
39
  NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
40
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
41
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
42
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
43
  NVIC_Init(&NVIC_InitStructure);
44
  
45
}
46
47
void GPIO_Configuration(void)
48
{
49
  GPIO_InitTypeDef GPIO_InitStructure;
50
51
  /* GPIOD DCF-Empfänger */
52
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
53
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
54
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
55
  GPIO_Init(GPIOD, &GPIO_InitStructure);
56
}
57
58
void RCC_Configuration(void)
59
{  
60
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
61
62
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO , ENABLE);
63
}
64
65
void TIM4_IRQHandler(void)
66
{
67
68
   /* Clear TIM4 Capture compare interrupt pending bit */
69
   TIM_ClearITPendingBit(TIM4, TIM_IT_CC2);
70
71
   /* Get the Input Capture value */
72
   IC2Value = TIM_GetCapture2(TIM4);
73
   IC2Value_1 = TIM_GetCapture1(TIM4);
74
75
   UsartSendChar((uint8_t)(IC2Value_1 >> 8));
76
   UsartSendChar((uint8_t)(IC2Value_1));
77
   UsartSendChar(0);
78
   UsartSendChar((uint8_t)(IC2Value >> 8));
79
   UsartSendChar((uint8_t)(IC2Value));
80
   UsartSendChar('\n');
81
}

von Tom S. (tom57)


Angehängte Dateien:

Lesenswert?

Hallo,
probiere mal den anhängenden Code.
DCF- Signal wird über PWM-Input eingelesen (per DMA in die Variablen 
geschrieben), gleichzeitig wird ein externer Int. ausgelöst, der die 
vorhergehenden Informationen auswertet.
Musst halt die Timer entsprechend Deiner Taktfrequenz anpassen. Bei mir 
läuft die CPU mit 96 MHz.

Gruß
Tom

von Tom S. (tom57)


Lesenswert?

Hallo nochmal,
ich war etwas voreilig - bzw. habe mich verlesen. Mein Code ist für 
einen STR912. Aber vielleicht gehts bei Dir ja auch - musst halt etwas 
umschreiben.

von Florian K. (f-kae)


Lesenswert?

Hi,

vielen Dank schon einmal für die Antwort.
1
  SCU_TIMCLKSourceConfig(SCU_TIM01,SCU_TIMCLK_INT); // Timer01 Clock form the prescaler
2
  SCU_TIMPresConfig(SCU_TIM01,((48000-1) &~ 1UL));    // Timer resolution 0,5ms
Ich nehme an, diese Zeilen sind interessant für mich. Ich kann dem Timer 
also zusätzlich zu dem PWM_Input einen Prescaler geben, um so die 
aufnehmbare Zeitspanne zu verlängern. Ich muss mal schauen wie es bei 
dem STM32F10X gemacht wird.

Warum in DMA schreiben? Bei der Frequenz ist eine schnelle Bearbeitung 
nicht wirklich notwendig, oder? Oder gibt es noch andere Gründe zur 
Nutzung von DMA.

Gruß,
Florian

von Tom S. (tom57)


Lesenswert?

Hi Florian,
ich benutze den DMA aus einem einfachen Grund, ich muss mich nicht in 
irgendeiner Interruptroutine rumplagen. So werden meine Variablen Puls 
und Periode automatisch aktualisiert. Das hat nur bei der Abarbeitung zu 
etwas Verwirrung geführt.
Der Systemclock ist der Timereingang bei mir. So kann man die Vorteiler 
in einem relativ großen Bereich nutzen.

Gruß und viel Spaß
Tom

von Florian K. (f-kae)


Lesenswert?

Hallo Tom,

mit der Zeile:
1
TIM4->PSC = 0x5DC0; //24000 -> 1Tick pro ms (1000Ticks pro Sekunde)

Kann ich nun mein DCF-Signal sauber per PWM-Input auslesen!
Es funktioniert nun also, danke! :)

OK, aber ganz verstehe ich nicht was dir das per DMA für einen Vorteil 
bringt. Der Interrupt-Handler läßt sich auch gut nutzen und ich benötige 
keinen zusätzlichen External-Interrupt.
1
void TIM4_IRQHandler(void){
2
3
/* Clear TIM3 Capture compare interrupt pending bit */
4
TIM_ClearITPendingBit(TIM4, TIM_IT_CC2);
5
IC2Value = TIM_GetCapture2(TIM4);
6
IC2Value_1 = TIM_GetCapture1(TIM4);
7
8
if (IC2Value != 0)
9
{
10
   /* Period of Low Signal in ms */
11
   DutyCycle = IC2Value_1; //Should be about 80-120 or 180-220
12
      
13
   /* Frequency computation in Hz * 10 */
14
   Frequency = SystemCoreClock / IC2Value / 2400;
15
}
16
else
17
{
18
   DutyCycle = 0;
19
   Frequency = 0;
20
}
21
DCF_new_second = 1;
22
}

Gruß,
Florian

von Peter D. (peda)


Lesenswert?

Florian K. schrieb:
> kann ich an einem STM32F100VBT6B über den PWM-Input-Mode ein
> DCF77-Signal einlesen?

Man muß nicht immer alles so kompliziert wie möglich machen.

Man kann ganz einfach in einem Timerinterrupt mitzählen, wie oft der Pin 
high oder low war.
Einen Timerinterrupt (z.B. 1ms oder 10ms) hat man ja eh in jeder 
Anwendung, da einfach den Code mit rein hängen.

Dann hat man auch kein Problem, die interne Zeit mit dem DCF zu 
synchronisieren. Da es der gleiche Interrupt ist, kann es zu keiner 
Race-Condition kommen.
Hat man verschiedene Interrupts, kann mal der eine und mal der andere 
zuerst kommen und plötzlich geht Deine Uhr eine Sekunde falsch.

von Tom S. (tom57)


Lesenswert?

Hallo Florian,
freut mich, dass Deine Funktion nun funktioniert.
Tja, warum DMA ?
Nun, ich werte im EXTINT2 die Zeitinformation gleich aus. Sprich, mit 
jeder negativen Flanke des DCF- Signals (habe keinen Inverter davor) 
wird die vorherige Information ausgewertet - und falls die Minute herum 
ist - auch die gesamte Zeitinformation verarbeitet. Ich brauche so also 
keine zusätzliche Funktion in den anderen Programmteilen.
Man muss es nicht so machen, hat bei mir aber zu einer Steigerung der 
Performance des Gesamtsystems geführt. Es geht ja nicht ausschliesslich 
nur um eine Uhr. :)

von Tom S. (tom57)


Lesenswert?

Peter Dannegger schrieb:
> Florian K. schrieb:
>> kann ich an einem STM32F100VBT6B über den PWM-Input-Mode ein
>> DCF77-Signal einlesen?
>
> Man muß nicht immer alles so kompliziert wie möglich machen.
>
> Man kann ganz einfach in einem Timerinterrupt mitzählen, wie oft der Pin
> high oder low war.
> Einen Timerinterrupt (z.B. 1ms oder 10ms) hat man ja eh in jeder
> Anwendung, da einfach den Code mit rein hängen.
>
> Dann hat man auch kein Problem, die interne Zeit mit dem DCF zu
> synchronisieren. Da es der gleiche Interrupt ist, kann es zu keiner
> Race-Condition kommen.
> Hat man verschiedene Interrupts, kann mal der eine und mal der andere
> zuerst kommen und plötzlich geht Deine Uhr eine Sekunde falsch.

Hallo Peter,
warum kompliziert ?
PWM-Input geht ohne Interrupt und wenn Du einen extra Interrupt auf die 
Pulsflanke auslöst, kannst Du dort gleich die Zeitinformation 
verarbeiten. Die interne RTC wird erst synchronisiert, wenn das 
DCF-Signal in Ordnung war und keine Fehler auftraten, und die RTC läuft 
absolut synchron.

von old man (Gast)


Lesenswert?

Tom Strippe schrieb:
> Hallo Peter,
> warum kompliziert ?
> PWM-Input geht ohne Interrupt und wenn Du einen extra Interrupt auf die
> Pulsflanke auslöst, kannst Du dort gleich die Zeitinformation
> verarbeiten. Die interne RTC wird erst synchronisiert, wenn das
> DCF-Signal in Ordnung war und keine Fehler auftraten, und die RTC läuft
> absolut synchron.

Und wenn mal aus dem Empfänger nur ein Rauschen kommt, kannst du den vom 
Interrupt getriggerten Sekundenzeiger als Ventilator benutzen...

Ich würde es auch immer wie Peter machen. Das ist bei solchen 
Eingangssignalen die einzige Variante die sich garantiert durch nichts 
aus der Ruhe bringen lässt und auch noch ziemlich tolerant ist mit dem 
Dreck auf dem Einganssignal.

von Peter D. (peda)


Lesenswert?

Tom Strippe schrieb:
> Bei mir
> läuft die CPU mit 96 MHz.

Bei einer so langsamen CPU kann man natürlich die riesige Datenrate von 
1Baud nur per DMA wuppen;-)
DMA mag ein lustiges Experiment sein, einen praktischen Nutzen hat es 
hier nicht.


Tom Strippe schrieb:
> ich benutze den DMA aus einem einfachen Grund, ich muss mich nicht in
> irgendeiner Interruptroutine rumplagen.

Das "Rumplagen" (Dekodieren) mußt Du trotzdem, Du hast es nur an eine 
andere Stelle verschoben. Statt im bestehenden schon anderweitig 
genutzten Timerinterrupt, in den zusätzlichen externen Interrupt.


Tom Strippe schrieb:
> Das hat nur bei der Abarbeitung zu
> etwas Verwirrung geführt.

Das kann ich mir gut vorstellen.

von Peter D. (peda)


Lesenswert?

old man schrieb:
> Und wenn mal aus dem Empfänger nur ein Rauschen kommt, kannst du den vom
> Interrupt getriggerten Sekundenzeiger als Ventilator benutzen...

Das sollte man nicht auf die leichte Schulter nehmen.
Ich hab schon Gerätsoftware abstürzen sehen, nur weil jemand ein Kabel 
zu stecken vergessen hat und dann der Eingang floatete und die CPU mit 
Interrupts flutete.
Das ist bei den ARM besonders tragisch, da die damit das Main komplett 
totlegen können.
Bei den AVRs läuft das Main noch, wenn auch langsam (1 Zyklus zwischen 
den Interrupts). Der AVR könnte z.B. eine Fehlermeldung anzeigen.

Beim ARM müßte man einen höher priorisierten Timer aufsetzen. Der 
externe Interrupt zählt eine Variable hoch und der Timer prüft, ob sie 
einen kritischen Wert überschreitet und disabled ihn dann.

: Bearbeitet durch User
von Florian K. (f-kae)


Lesenswert?

Danke für die weiteren Beiträge, ich habe das DCF-Signal schon 
erfolgreich per Software eingelesen, alle 4ms das Signal abtasten und 
per external interrupt jede Sekunde die Auswertung...

Ich wollte mich nur mit dem PWM_Input auch noch beschäftigen, da ich 
dachte es wäre die elegantere Methode, auch wenn es bei dieser Anwendung 
völlig unerheblich ist, wie ich das DCF-Signal erhalte.

old man schrieb:
> Ich würde es auch immer wie Peter machen. Das ist bei solchen
> Eingangssignalen die einzige Variante die sich garantiert durch nichts
> aus der Ruhe bringen lässt und auch noch ziemlich tolerant ist mit dem
> Dreck auf dem Einganssignal.

Was genau meinst du damit? Wo ist das Problem wenn das Signal mal 
verrauscht ist? Mich interessieren die Argumente dagegen wirklich. :)
Ich bin davon ausgegangen, dass das einlesen per PWM_Input grundsätzlich 
effizienter ist als die Abtastung... und daher eher angewendet würde.

Ich arbeite noch nicht so lange mir µC und bin daher noch sehr 
lernbegierig :P

edit:
Peter Dannegger schrieb:
> die CPU mit
> Interrupts flutete.
Bei der PWM_Input Variante wird also eher unkontrolliert das 
TIM4-Interrupt ausgelöst und könnte theoretisch wenn das Signal am 
DCF-Eingang eine verrauschte Frequenz von 24MHz hätte mein Programm lahm 
legen. Ist das die Entscheidung dagegen?

: Bearbeitet durch User
von old man (Gast)


Lesenswert?

Florian K. schrieb:
> Was genau meinst du damit? Wo ist das Problem wenn das Signal mal
> verrauscht ist? Mich interessieren die Argumente dagegen wirklich. :)


das bezog sich darauf:

Tom Strippe schrieb:
> PWM-Input geht ohne Interrupt und wenn Du einen extra Interrupt auf die
> Pulsflanke auslöst,

Wenn man mit dem Eingangssignal externe Interrupts an der Fanke auslöst, 
dann ist nicht mehr vorhersehbar was passiert wenn das Eingangssignal 
nur ein ordentliches Rauschen ist.

von Peter D. (peda)


Lesenswert?

Florian K. schrieb:
> Ich bin davon ausgegangen, dass das einlesen per PWM_Input grundsätzlich
> effizienter ist als die Abtastung

Ich würde mal sagen, alles was man mit 1ms oder länger abtasten kann, 
stellt für den ARM keine merkbare Last dar.
D.h. für solche langsamen Signale Signale würde ich als effizienter den 
kleineren Code ansehen, gegenüber <0,001% CPU-Lastunterschied.

von old man (Gast)


Lesenswert?

Florian K. schrieb:
> Bei der PWM_Input Variante wird also eher unkontrolliert das
> TIM4-Interrupt ausgelöst und könnte theoretisch wenn das Signal am
> DCF-Eingang eine verrauschte Frequenz von 24MHz hätte mein Programm lahm
> legen. Ist das die Entscheidung dagegen?

Jetzt hast du die Antwort in der Zwischenzeit schon selbst gegeben. 
Genau das ist der Punkt. Es ist nicht mehr absehbar wie viele Interrupts 
am Ende wirklich kommen. Das kann natürlich soweit gehen, dass der 
Controller für was anderes als diesen Interrupt keine Zeit mehr hat. 
Natürlich ist das hypothetisch, aber wir reden hier nicht von 
Hallsensoren oder so was, sondern von Funkempfängern. Und bei denen 
sollte man im allgemeinen mit allem Rechnen.

von old man (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Ich würde mal sagen, alles was man mit 1ms oder länger abtasten kann,
> stellt für den ARM keine merkbare Last dar.
> D.h. für solche langsamen Signale Signale würde ich als effizienter den
> kleineren Code ansehen, gegenüber <0,001% CPU-Lastunterschied.

Das kann ich nur unterstützen. Und in dem selben Kontext können dann 
auch noch alle Tasten, Drehencoder oder sonstige mechanischen Sachen 
abgetastet werden. Das ergibt immer reproduzierbare Durchlaufzeiten und 
hat den Vorteil nur die Gpio-Eingänge selbst zu benötigen und keine 
andere Hardware als den Timer für den zykl. Interrupt. Wenn man das in 
der Art und Weise macht ist auch die Portierung von solchen 
Codebestandteilen zwischen den verschiedenen Controllern und 
Architekturen ein Kinderspiel. Auch im Layout braucht man sich weniger 
Gedanken machen, da es so ziemlich egal ist an welchen Pin das hängt.

von Tom S. (tom57)


Lesenswert?

Peter Dannegger schrieb:
> Tom Strippe schrieb:
>> Bei mir
>> läuft die CPU mit 96 MHz.
>
> Bei einer so langsamen CPU kann man natürlich die riesige Datenrate von
> 1Baud nur per DMA wuppen;-)
> DMA mag ein lustiges Experiment sein, einen praktischen Nutzen hat es
> hier nicht.
>
>
> Tom Strippe schrieb:
>> ich benutze den DMA aus einem einfachen Grund, ich muss mich nicht in
>> irgendeiner Interruptroutine rumplagen.
>
> Das "Rumplagen" (Dekodieren) mußt Du trotzdem, Du hast es nur an eine
> andere Stelle verschoben. Statt im bestehenden schon anderweitig
> genutzten Timerinterrupt, in den zusätzlichen externen Interrupt.
>
>
> Tom Strippe schrieb:
>> Das hat nur bei der Abarbeitung zu
>> etwas Verwirrung geführt.
>
> Das kann ich mir gut vorstellen.
 Sehr geehrter Herr Dannegger,
wie es scheint hast Du das Programm nicht verstanden - ich helfe gerne 
nach.
Bei mir gibt es keinen, ich wiederhole, KEINEN, Timerinterrupt. Der 
externe Interrupt wird lediglich dazu verwendet den letzten empfangenen 
Sekundenimpuls auf Plausibilität zu testen und, sofern es der 
Minutentakt war, die Zeit dann auszuwerten.
Bitte vor dem Meckern und kritisieren und meinemethodeistehdiebeste 
Geschwätz nachlesen.

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.