Forum: Mikrocontroller und Digitale Elektronik STM32-Discovery - Implementierung PI-Regelung (für Aufwärtswandler) - Problem: ADC zu langsam


von R. W. (Gast)


Lesenswert?

Hallo liebe Community,

für ein Studienprojekt programmieren wir eine Regelung für einen 
Aufwärtswandler mit einem STM32 Discovery.

Der Aufwärtswandler soll mit einer Frequenz von 40kHz getaktet werden. 
Das Tastverhältnis ist dabei von zwei analogen, eingelesenen Werten 
abhängig(Strom und Spannung).

Unsere Idee ist, den ADC über einen Timer-Interrupt alle 25µs (1/40kHz) 
einzulesen und dann an die Regelung weiterzugeben. In der ISR befinden 
sich dann die Funktionen zum Abrufen des ADC Wertes und zum Aufruf der 
Regelung. Zudem toggeln wir einen Pin, um zu überprüfen, dass das 
Programm alle 25µs in die ISR springt. Letzteres ist durch die 
Implementierung der Funktion zum Abrufen des ADC Wertes nicht mehr 
gegeben, da der Pin dann nur noch alle 142µs getoggelt wird.

Folglich dauert unsere AD-Wandlung zu lange. Hat jemand eine Idee zur 
Lösung unseres Problems.


Es ist unser erster Post. Ich hoffe das ist so okay. Sollte ihr noch 
Fragen haben, beantworten wir die euch gerne.
Hier der Code(Projekt im Anhang):
1
ADCInit.c :
2
3
#include "ADCInit.h"
4
5
void ADCInit()
6
{
7
8
  RCC_ADCCLKConfig(RCC_PCLK2_Div4);
9
10
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO | RCC_APB2Periph_ADC1, ENABLE);
11
12
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
13
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
14
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
15
  GPIO_Init(GPIOC, &GPIO_InitStructure);
16
17
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
18
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
19
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
20
  GPIO_Init(GPIOC, &GPIO_InitStructure);
21
22
23
  ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
24
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
25
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
26
  ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
27
  ADC_InitStructure.ADC_NbrOfChannel = 2;
28
  ADC_InitStructure.ADC_ScanConvMode = DISABLE;
29
  ADC_Init(ADC1, &ADC_InitStructure);
30
31
  ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_1Cycles5);
32
  ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 2, ADC_SampleTime_1Cycles5);
33
34
  ADC_Cmd(ADC1, ENABLE);
35
  ADC_DMACmd(ADC1, ENABLE);
36
37
  ADC_ResetCalibration(ADC1);
38
  while(ADC_GetResetCalibrationStatus(ADC1));
39
  ADC_StartCalibration(ADC1);
40
  while(ADC_GetCalibrationStatus(ADC1));
41
}
42
43
44
void DMAInit.c:
45
46
#include "DMAInit.h"
47
48
49
50
void DMAInit()
51
{
52
53
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
54
55
  DMA_InitStructure.DMA_BufferSize = 2;
56
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
57
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
58
  DMA_InitStructure.DMA_MemoryBaseAddr = (u32)adc_buffer;
59
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
60
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
61
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
62
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
63
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
64
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
65
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
66
  DMA_Init(DMA1_Channel1, &DMA_InitStructure);
67
68
  DMA_Cmd(DMA1_Channel1, ENABLE);
69
70
71
GETADC.c:
72
73
#include "GETADC.h"
74
75
76
void GETADC()
77
{
78
  ADC_SoftwareStartConvCmd(ADC1, ENABLE);  //starts ADC
79
80
  while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)){} //wait for conversion complete
81
82
83
  if(adc_buffer[0]>0)
84
  {
85
    GPIO_WriteBit(GPIOC, GPIO_Pin_8, SET);
86
  }
87
88
  else
89
  {
90
    GPIO_WriteBit(GPIOC, GPIO_Pin_8, RESET);
91
  }
92
93
  ADC_ClearFlag(ADC1, ADC_FLAG_EOC);    //clear EOC Flag
94
95
}
96
97
#include "LEDInit.h"
98
99
100
LEDInit.c:
101
102
void LEDInit()
103
{
104
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
105
106
    GpioStructure.GPIO_Pin = GPIO_Pin_0;
107
    GpioStructure.GPIO_Mode = GPIO_Mode_Out_PP;
108
    GpioStructure.GPIO_Speed = GPIO_Speed_50MHz;
109
    GPIO_Init(GPIOB, &GpioStructure);
110
}
111
112
113
#include "InitPIControl.h"
114
115
int i=0;
116
  float e;
117
  float delta_u;
118
  float u;
119
  float e1;
120
  float ki=50;
121
  float kp=0.5;
122
  float k1;
123
  float k2;
124
  double uint;
125
  int newPulse;
126
127
128
PIControl.c:
129
130
131
int PIControl()
132
{
133
134
135
//  k1=kp+ki;
136
//  k2=-kp;
137
//
138
//
139
//
140
//    if(i==0)
141
//      {
142
//        e1=0;
143
//
144
//        e = (adc_buffer[0]*0.0196)-40; //Berechnung der Abweichung
145
//        delta_u = k1*e + k2*e1; // PID algorithm (3.17)
146
//        u = u + delta_u;
147
//        i=1;
148
//      }
149
//
150
//      else
151
//      {
152
//        e1 = e;
153
//        e = (adc_buffer[0]*0.0196)-40; //Berechnung der Abweichung
154
//        delta_u = k1*e + k2*e1; // PID algorithm (3.17)
155
//        u = u + delta_u;
156
//
157
//      }
158
159
      //uint = (int)u;
160
161
      uint= 0.5;
162
      newPulse = (int)(uint*99);
163
164
      return newPulse;
165
166
167
168
169
170
}
171
172
#include "PWMInit.h"
173
#include "InitPIControl.h"
174
175
176
int newPulse;
177
178
179
PWMInit.c:
180
181
void PWMInit()
182
{
183
184
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); //Einschalten des Taktsignals für PortA sowie AFIO Register
185
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //Einschalten des Taktsignals des Timers 2
186
187
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //Wahl der Mode Alternate Function Push-Pull
188
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;  //Auswahl des Pins, hier PIN PA0
189
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  ////Einstellung der Geschwindigkeit, hier 50MHz
190
  GPIO_Init(GPIOB, &GPIO_InitStructure);  //Initialisiert die eben zuvor eingestellten Werte von Mode,Pin, Speed
191
192
  TIM_TimeBase_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;  // Vorteiler für Takt, aber nur bei externer Clock notwendig
193
  TIM_TimeBase_InitStructure.TIM_CounterMode = TIM_CounterMode_Up; // Einstellen der Zähler-Mode, hier Aufwärtszählen
194
  TIM_TimeBase_InitStructure.TIM_Period = 99;  //Wert bis zu dem Timer zählt
195
  TIM_TimeBase_InitStructure.TIM_Prescaler = 5 ;  // Teilt den Eingangstakt(24MHz) um den Faktor (TIM_Prescaler+1), hier also 24
196
  TIM_TimeBaseInit(TIM4, &TIM_TimeBase_InitStructure); // Initialisiert die eben zuvor eingestellten Werte von Clock Division, Counter Mode, Period, Prescaler
197
198
  TIM_OC_InitStructure.TIM_OCMode = TIM_OCMode_PWM1;  //Einstellen der PWM-Funktionalität, bei PWM1 liegt Ausgang während der Pulsdauer auf high und anschließend auf low, PWM2 wäre invertiert
199
  TIM_OC_InitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;  //
200
  TIM_OC_InitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Set;  //
201
  TIM_OC_InitStructure.TIM_OCPolarity = TIM_OCPolarity_High;  //
202
  TIM_OC_InitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;  //
203
  TIM_OC_InitStructure.TIM_OutputState = TIM_OutputState_Enable;  //
204
  TIM_OC_InitStructure.TIM_OutputNState = TIM_OutputNState_Disable;  //
205
  TIM_OC_InitStructure.TIM_Pulse = newPulse;  //Einstellen der Pulsdauer, da Tastverhältnis 50%-->TIM_Pulse=TIM_PERIOD/2
206
  TIM_OC2Init(TIM4, &TIM_OC_InitStructure);  //Initialisiert die zuvor eingestellten Werte für Channel 1(es gibt noch 3 weitere)
207
208
  TIM_Cmd(TIM4, ENABLE);  //Aktiviert den TIMER
209
210
}
211
212
213
TIMERInit.c:
214
215
#include "TIMERInit.h"
216
217
218
void TIMERInit()
219
{
220
221
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
222
223
  TIM_TimeBaseInitTypeDef TimerInitStructure;
224
225
  TimerInitStructure.TIM_Prescaler = 2;
226
  TimerInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
227
  TimerInitStructure.TIM_Period = 99;
228
  TimerInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
229
  TimerInitStructure.TIM_RepetitionCounter = 0;
230
  TIM_TimeBaseInit(TIM3, &TimerInitStructure);
231
  TIM_Cmd(TIM3, ENABLE);
232
233
  TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
234
235
}
236
237
#include "INTERRUPTInit.h"
238
239
INTERRUPTInit.c:
240
241
void EnableTimerInterrupt()
242
{
243
    NvicStructure.NVIC_IRQChannel = TIM3_IRQn;
244
    NvicStructure.NVIC_IRQChannelPreemptionPriority = 0;
245
    NvicStructure.NVIC_IRQChannelSubPriority = 1;
246
    NvicStructure.NVIC_IRQChannelCmd = ENABLE;
247
    NVIC_Init(&NvicStructure);
248
}
249
250
251
void TIM3_IRQHandler()
252
{
253
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
254
    {
255
256
      GPIOB->ODR ^= (GPIOB, GPIO_Pin_0);
257
      GETADC();
258
      PIControl();
259
      TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
260
261
262
    }
263
}
264
265
266
267
main:
268
269
#include "ADCInit.h"
270
#include "DMAInit.h"
271
#include "INTERRUPTInit.h"
272
#include "TIMERInit.h"
273
#include "GETADC.h"
274
#include "LEDInit.h"
275
#include "PWMInit.h"
276
#include "InitPIControl.h"
277
278
int main(void)
279
{
280
  SystemInit();
281
  LEDInit();
282
  DMAInit();
283
  ADCInit();
284
  //GETADC();
285
  EnableTimerInterrupt();
286
  TIMERInit();
287
288
289
290
    while(1)
291
    {
292
      PWMInit();
293
    }
294
}



Vielen Dank und viele Grüße
Die blutigen Anfänger ;)

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


Lesenswert?

Mache zu erst einmal eine Analyse wie lange der zeitkritische Code denn 
überhaupt benötigt. Hier ein Demo "Taktzeitberechnung und Überwachung":
http://www.mikrocontroller.net/articles/STM32_f%C3%BCr_Einsteiger#Taktzeitberechnung_und_.C3.9Cberwachung

Ansonsten würde ich die AD Wandlung nicht mit dem Interrupt erst 
starten, sondern nonstop laufen lassen und mittels DMA irgend wo hin 
kopieren.
Somit stehen die aktuellen Werte immer schon von alleine im RAM und man 
muss nicht extra darauf warten.

: Bearbeitet durch User
von Benjamin K. (bentschie)


Lesenswert?

Wieso muss die AD Wandlung genauso schnell wie die PWM-Ausgabe sein?

Wir machen z.B. eine PWM mit 16 kHz, wobei die Regelung und dabei auch 
die AD Wandlung nur mit 1 kHz läuft. Da wird dann halt das gleiche 
DutyCycle für mehrere Perioden ausgegeben.

von Vn N. (wefwef_s)


Lesenswert?

Benjamin K. schrieb:
> Wir machen z.B. eine PWM mit 16 kHz, wobei die Regelung und dabei auch
> die AD Wandlung nur mit 1 kHz läuft. Da wird dann halt das gleiche
> DutyCycle für mehrere Perioden ausgegeben.

Erstens das.

Zweitens: wie schnell der ADC ist, findet man nicht per Trial and Error 
heraus, sondern mithilfe des Datenblattes.

Warum keimt da nur der Verdacht aus, dass dieses Programm nicht nur so 
zusammenkopiert aussieht, sondern auch ist?

von R. W. (Gast)


Lesenswert?

Markus Müller schrieb:
> Mache zu erst einmal eine Analyse wie lange der zeitkritische Code denn
> überhaupt benötigt. Hier ein Demo "Taktzeitberechnung und Überwachung":
> 
http://www.mikrocontroller.net/articles/STM32_f%C3%BCr_Einsteiger#Taktzeitberechnung_und_.C3.9Cberwachung
>
> Ansonsten würde ich die AD Wandlung nicht mit dem Interrupt erst
> starten, sondern nonstop laufen lassen und mittels DMA irgend wo hin
> kopieren.
> Somit stehen die aktuellen Werte immer schon von alleine im RAM und man
> muss nicht extra darauf warten.

Danke für deine schnelle Antwort Markus.
Wir haben versucht das mit der Taktzeitberechnung in den Code zu 
integrieren. Leider bekommen wir dann Fehlermeldungen("undefined 
reference to `CORE_ClearSysTick'" und "undefined reference to 
`CORE_GetSysTick").

Auch möchten wir dir für den Tipp danken, den ADC-Wert ausserhalb des 
Interrupts aufzurufen!

Dennoch bleibt die Frage offen, warum der ADC solange braucht.
Der Bus an dem der ADC hängt, läuft mit 24MHz. Wir haben einen clock 
divider von 4 eingestellt, wodurch eine Taktfrequenz von 6MHz übrig 
bleibt. Wie lasen, dass die ADC-Wandlung mit 12,5 Zyklen angegeben ist, 
die sample-Time haben wir auf das Minimum von 1,5 Zyklen eingestellt.
Folglich sollte der ADC mit einer Frequenz von über 400kHz arbeiten und 
damit eigentlich schnell genug sein, oder haben wir als absolute 
Anfänger da was falsch verstanden?



Benjamin K. schrieb:
>Wieso muss die AD Wandlung genauso schnell wie die PWM-Ausgabe sein?

>Wir machen z.B. eine PWM mit 16 kHz, wobei die Regelung und dabei auch
>die AD Wandlung nur mit 1 kHz läuft. Da wird dann halt das gleiche
>DutyCycle für mehrere Perioden ausgegeben.

Auch dir vielen Dank für deine schnelle Antwort. Wir haben deinen 
Vorschlag mal in unserer Matlab/Simulink Simulation getestet, mit dem 
Ergebnis, dass es eine Alternative darstellen könnte. Darf man fragen wo 
diese Regelung bei dir zum Einsatz kommt? Wir sehen bei unserem 
Aufwärtswandler den Spulenstrom als kritisches Element. Der Spulenkörper 
ist knapp dimensioniert und könnte bei zu hohem Strom in die Sättigung 
gehen. Lieber würden wir die PWM mit 40kHz nachregeln - oder zumindest, 
so nah wie möchlich an diesen Wert herankommen.
Hast du die Regelung auch mit einem STM32 realisiert? Wie lange brauch 
dein AD-Wandler?

vn nn schrieb:

>Erstens das.

>Zweitens: wie schnell der ADC ist, findet man nicht per Trial and Error
>heraus, sondern mithilfe des Datenblattes.

>Warum keimt da nur der Verdacht aus, dass dieses Programm nicht nur so
>zusammenkopiert aussieht, sondern auch ist?

vn nn, auch dir möchten wir für deinen Beitrag danken. Sehr richtig hast 
du erkannt, das Teile des Codes kopiert und für unsere Zwecke 
abgewandelt wurden. Wie sagt man so schön: "Das Rad muss ja nicht neu 
erfunden werden."
Letzten Endes sind wir angehende Energietechniker und unsere Erfahrungen 
mit Mikrocontrollern sind gering. Fokus unsere Arbeit lag 
dementsprechend nicht auf der Programmierung, sondern auf dem 
Schaltungsentwurf und dessen Realisierung.

Weiter oben haben wir beschrieben, was wir dem Datenblatt bezüglich 
dessen entnehmen konnten. Offensichtlich spielen allerdings weitere 
Faktoren eine Rolle, in der Berechnung der Geschwindigkeit des ADCs.

Für weitere konstruktive Kritik und Tipps, wäre ich auch dir sehr 
dankbar.

Viele Grüße
R. W.

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


Lesenswert?

Ich dachte das ist in der CMSIS bereits drin. Hier die Defines:
1
// Sys-Tick Counter - Messen der Anzahl der Befehle des Prozessors:
2
#define CORE_SysTickEn()      (*((u32*)0xE0001000)) = 0x40000001
3
#define CORE_SysTickDis()      (*((u32*)0xE0001000)) = 0x40000000
4
#define CORE_GetSysTick()      (*((u32*)0xE0001004))
5
#define CORE_ClearSysTick()      (*((u32*)0xE0001004)) = 0

von R. W. (Gast)


Lesenswert?

Markus Müller schrieb:
> Ich dachte das ist in der CMSIS bereits drin. Hier die Defines:
> // Sys-Tick Counter - Messen der Anzahl der Befehle des Prozessors:
> #define CORE_SysTickEn()      (*((u32*)0xE0001000)) = 0x40000001
> #define CORE_SysTickDis()      (*((u32*)0xE0001000)) = 0x40000000
> #define CORE_GetSysTick()      (*((u32*)0xE0001004))
> #define CORE_ClearSysTick()      (*((u32*)0xE0001004)) = 0

Vielen Dank Markus. Wir haben mal die Zeit gemessen, wie lange der 
Befehl braucht, um die Funktion GETADC() auszuführen. Für iZ haben wir 
ca. 1000 Maschinentakte gemessen. Also sollte sich die Zeit wie folgt 
berechnen lassen:

1000/6MHz=166,67µs --> 6000Hz
(6 MHz, weil wir die ADC-Clock durch 4 
teilen->RCC_ADCCLKConfig(RCC_PCLK2_Div4))

Warum werden denn so viele Maschinentakte benötigt?

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


Lesenswert?

Die Maschinentakte sind schon richtig, aber die Zeit ist falsch.
Ich weiß jetzt nicht genau welcher STM32 auf dem Demo-Board steckt und 
wie "SystemInit();" den STM32 initialisiert.

Somit sollte man:
1000 / 24MHz = 41,6µS
rechnen. (CPU Takt und nicht AD Takt)

Wie While Schleife in GETADC() benötigt die Takte.

Edit: Wenn Ihr nicht genau wisst mit welcher Frequenz der µC arbeitet, 
so kann diese auf den MCO Pin ausgegeben werden.

: Bearbeitet durch User
von R. W. (Gast)


Lesenswert?

Markus Müller schrieb:
> Die Maschinentakte sind schon richtig, aber die Zeit ist falsch.
> Ich weiß jetzt nicht genau welcher STM32 auf dem Demo-Board steckt und
> wie "SystemInit();" den STM32 initialisiert.
>
> Somit sollte man:
> 1000 / 24MHz = 41,6µS
> rechnen. (CPU Takt und nicht AD Takt)
>
> Wie While Schleife in GETADC() benötigt die Takte.
>
> Edit: Wenn Ihr nicht genau wisst mit welcher Frequenz der µC arbeitet,
> so kann diese auf den MCO Pin ausgegeben werden.

CPU Takt sind 24MHz. Wir haben mit dem AD Takt gerechnet. Dann nochmals 
vielen Dank, wir können morgen erst alles mit einem Oszi genauer testen.

von Embedded (Gast)


Lesenswert?

Tut Euch einen Gefallen und triggert den ADC mit dem PWM-Timer. Der 
PWM-Timer läuft dabei im Center Aligned Mode und der ADC wird genau im 
Mittelpunkt getriggert. Der Regler wird dann im ADC-Interrupt 
ausgeführt. Damit habt ihr die geringstmögliche Totzeit und schließt 
alle Effekte durch den Schaltripple aus. Außerdem würde ich die 
Messungen mehrfach wiederholen (mindestens 2, besser 4 Messungen pro 
Kanal) und eine Mittelung durchführen. Damit wird man etwas unanfälliger 
gegenüber Störungen.

von R. W. (Gast)


Lesenswert?

Embedded schrieb:
> Tut Euch einen Gefallen und triggert den ADC mit dem PWM-Timer. Der
> PWM-Timer läuft dabei im Center Aligned Mode und der ADC wird genau im
> Mittelpunkt getriggert. Der Regler wird dann im ADC-Interrupt
> ausgeführt. Damit habt ihr die geringstmögliche Totzeit und schließt
> alle Effekte durch den Schaltripple aus. Außerdem würde ich die
> Messungen mehrfach wiederholen (mindestens 2, besser 4 Messungen pro
> Kanal) und eine Mittelung durchführen. Damit wird man etwas unanfälliger
> gegenüber Störungen.

Vielen Dank für den Tipp. Macht alles sehr viel Sinn, was du sagst. 
Werden versuchen, dass mit unseren amateurhaften Programmierfertigkeiten 
umzusetzen.

@Markus: Ganz vergessen zu erwähnen, dass sich durch weglassen der 
while-Schleife die Maschinentakte auf etwa 50 reduziert haben.

von Falk B. (falk)


Lesenswert?

@ R. W. (boost)

>@Markus: Ganz vergessen zu erwähnen, dass sich durch weglassen der
>while-Schleife die Maschinentakte auf etwa 50 reduziert haben.

Bist du einer der üblichen Trolle oder doch ein sprudelnder Quell 
Realsatire?
Waum meinst du, kann man diese while-Schleife weglassen? Do you speak 
english? At least some Brocken?
1
  while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)){} //wait for conversion complete

von R. W. (Gast)


Lesenswert?

Falk Brunner schrieb:
> @ R. W. (boost)
>
>>@Markus: Ganz vergessen zu erwähnen, dass sich durch weglassen der
>>while-Schleife die Maschinentakte auf etwa 50 reduziert haben.
>
> Bist du einer der üblichen Trolle oder doch ein sprudelnder Quell
> Realsatire?
> Waum meinst du, kann man diese while-Schleife weglassen? Do you speak
> english? At least some Brocken?
>
>
1
>   while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)){} //wait for conversion 
2
> complete
3
>

Hallo Falk,
du hast schon Recht, wir hatten nicht erwähnt, dass wir den ADC in 
continous-Mode geändert haben. Werden dann nicht ständig Werte des ADC 
in unsere Variable adc_buffer geschrieben?

Wenn die while-Schleife vonnöten ist, gibt es eine schnellere 
Möglichkeit zu überprüfen, dass die Umwandlung abgeschlossen ist?

von Falk B. (falk)


Lesenswert?

@ R. W. (boost)

>du hast schon Recht, wir hatten nicht erwähnt, dass wir den ADC in
>continous-Mode geändert haben.

Aha. Kleine, aber entscheidene Information!

> Werden dann nicht ständig Werte des ADC
>in unsere Variable adc_buffer geschrieben?

Eher nicht. Die neuen Messergebnisse landen im Ergebnisregister des ADC, 
nicht in einer Variablen. (Wenn es nicht irgendein Stück SOftware gibt, 
welches die Daten kopiert, keine Ahnung was in der Bibliothek 
drinsteckt).

>Wenn die while-Schleife vonnöten ist, gibt es eine schnellere
>Möglichkeit zu überprüfen, dass die Umwandlung abgeschlossen ist?

Man muss einfach wissen, dass die fertig ist, weil sie fertig sein MUSS. 
Wie das geht, wurde hier schon angesprochen.

Beitrag "Re: STM32-Discovery - Implementierung PI-Regelung (für Aufwärtswandler) - Problem: ADC zu langsam"

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


Lesenswert?

Ja, werden aktualisiert.

Besser mehrere Werte mittels DMA in das RAM kopieren und dann mitteln.

von Embedded (Gast)


Lesenswert?

R. W. schrieb:
> du hast schon Recht, wir hatten nicht erwähnt, dass wir den ADC in
> continous-Mode geändert haben. Werden dann nicht ständig Werte des ADC
> in unsere Variable adc_buffer geschrieben?

Trotzdem braucht der ADC logischerweise eine endliche Zeit, um ein neues 
Ergebnis zu liefern!

R. W. schrieb:
> Wenn die while-Schleife vonnöten ist, gibt es eine schnellere
> Möglichkeit zu überprüfen, dass die Umwandlung abgeschlossen ist?

Nimm doch einen Interrupt!

von R. W. (Gast)


Lesenswert?

Embedded schrieb:
> R. W. schrieb:
>> du hast schon Recht, wir hatten nicht erwähnt, dass wir den ADC in
>> continous-Mode geändert haben. Werden dann nicht ständig Werte des ADC
>> in unsere Variable adc_buffer geschrieben?
>
> Trotzdem braucht der ADC logischerweise eine endliche Zeit, um ein neues
> Ergebnis zu liefern!
>
> R. W. schrieb:
>> Wenn die while-Schleife vonnöten ist, gibt es eine schnellere
>> Möglichkeit zu überprüfen, dass die Umwandlung abgeschlossen ist?
>
> Nimm doch einen Interrupt!

Verstehen wir dich richtig? Du schlägst vor mit der EOC-Flag den 
Interrupt auszulösen und darin die Regelung laufen zu lassen? Uns tut 
die blöde Frage leid, aber wie du bestimmt schon gemerkt hast, sind wir 
Anfänger.

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


Lesenswert?

R. W. schrieb:
> aber wie du bestimmt schon gemerkt hast, sind wir
> Anfänger.

Daher erst mal das AD-Wandler DMA-Beispiel von ST nehmen und die Werte 
beim Aufruf von GETADC() mitteln.

So kann man sich langsam an das gewünschte Endergebnis ran tasten und 
dabei lernen.

Wenn Ihr morgen wieder ein Oszi habt, so kann man auch Port-Pins 
setzen/löschen bei bestimmten Events, z.B. wenn GETADC() beginnt setzen 
und wenn GETADC() endet wieder löschen und man kann so schön sehen 
welche Routinen nacheinander aufgerufen werden und wie lange die 
brauchen, ist Interessant bei verschachtelten Interrupts die Asynchon 
kommen.

: Bearbeitet durch User
von Embedded (Gast)


Lesenswert?

R. W. schrieb:
> Verstehen wir dich richtig? Du schlägst vor mit der EOC-Flag den
> Interrupt auszulösen und darin die Regelung laufen zu lassen? Uns tut
> die blöde Frage leid, aber wie du bestimmt schon gemerkt hast, sind wir
> Anfänger.

Ja, genau.

von R. W. (Gast)


Lesenswert?

Markus Müller schrieb:
> R. W. schrieb:
>> aber wie du bestimmt schon gemerkt hast, sind wir
>> Anfänger.
>
> Daher erst mal das AD-Wandler DMA-Beispiel von ST nehmen und die Werte
> beim Aufruf von GETADC() mitteln.
>
> So kann man sich langsam an das gewünschte Endergebnis ran tasten und
> dabei lernen.
>
> Wenn Ihr morgen wieder ein Oszi habt, so kann man auch Port-Pins
> setzen/löschen bei bestimmten Events, z.B. wenn GETADC() beginnt setzen
> und wenn GETADC() endet wieder löschen und man kann so schön sehen
> welche Routinen nacheinander aufgerufen werden und wie lange die
> brauchen, ist Interessant bei verschachtelten Interrupts die Asynchon
> kommen.

Embedded schrieb:
> R. W. schrieb:
>> Verstehen wir dich richtig? Du schlägst vor mit der EOC-Flag den
>> Interrupt auszulösen und darin die Regelung laufen zu lassen? Uns tut
>> die blöde Frage leid, aber wie du bestimmt schon gemerkt hast, sind wir
>> Anfänger.
>
> Ja, genau.

Vielen Dank euch beiden. Wir werden das alles morgen mal ausprobieren.

von R. W. (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,

wir führen aktuell alle 100µs die unten stehende Befehle aus.
Dabei wird zunächst der aktuelle Wert des ADC in der Variablen 
VoltageValue gespeichert, die dann an die Funktion PIControl 
weitergegeben wird. Dort wird anschließend je nach gemessener Spannung 
eine neue Pulsdauer für das PWM-Signal berechnet. Die Pulsdauer wird 
daraufhin mit der Funktion ChangePulseWidth() neu eingestellt. 
Allerdings bekommen wir kein sauberes PWM Signale erzeugt. Während die 
positive Flanke noch deutlich und sauber auf dem Oszi erkennbar ist, so 
scheint sich die negative Flanke ständig hin und her zu 
verschieben(selbst wenn die Eingangsspannung unverändert bei 0V liegt 
und die Pulsdauer somit unverändert bleibt).
Das ausgegebene Signal ist im Anhang Foto2 zu entnehmen.

Sobald wird die Befehle nur jede Sekunde ausführen, erhalten wir ein 
sauberes PWM Signal mit der entsprechenden Pulsdauer(siehe Foto1).

Wir würden die neu erzeugte Pulsdauer jedoch alle 100µs(bzw. noch 
schneller) neu schreiben. Weiß jemand woran es liegen könnte, warum das 
nicht funktioniert?

Wenn wir das Programm debuggen, läuft alles wie geplant(Variablen haben 
alle den entsprechenden korrekten Wert und das Ausgangssignal sieht so 
aus wie es aussehen soll).



1
VoltageValue = ADC_ConvertedValue[0];
2
PIControl(VoltageValue);
3
ChangePulseWidth();


ChangePulseWidth-Funktion:
1
TIM_OCInitStructure.TIM_Pulse = newPulse;  
2
TIM_OC2Init(TIM4, &TIM_OCInitStructure);


Schöne Grüße

von GeGe (Gast)


Lesenswert?

R. W. schrieb:
> Wir würden die neu erzeugte Pulsdauer jedoch alle 100µs(bzw. noch
> schneller) neu schreiben. Weiß jemand woran es liegen könnte, warum das
> nicht funktioniert?

Vermutlich schaukelt sich die Regelung auf.
Wenn die Ausgangsgröße eingelesen wird, kann die ja noch eine Weile 
weiter ansteigen (oder absinken) bis endlich per Software 
entgegengewirkt wird. Wenn es dann soweit ist muss das ganze dann eben 
in die Gegenrichtung, welche dann nun Überproportional ausfällt.
Somit schwingt das Ganze.

Ich denke die bessere Lösung wäre hier den "analog watchdog" zu 
verwenden.

von R. W. (Gast)


Lesenswert?

Danke für deine Antwort gege. Die Funktion PIControl enthält aktuell 
noch nicht die Regelung. Die Werte vom ADC kommen noch von einer 
Gleichspannungsquelle.   In PIControl wird lediglich geschaut wie hoch 
die Spannung ist und dementsprechend gesagt, bei z.B. 1V soll das 
tastverhältnis 20% sein, bei 2V 40%,...

von Ingo (Gast)


Lesenswert?

Als erstes würde ich mir eine debug Ausgabe machen und diese über RS232 
ausgeben, damit ihr euch die ADC werte mal angucken könnt...

Dann würde ich min 4 mal so schnell messen wie Regeln.

Daher würde ich tatsächlich die Messung durch den PWM Interrupt triggern 
und jedes 4 mal oder so den Regler starten. Wichtig ist das ihr das in 
eurem Zeitfenster alles hinbekommt. Float würde ich auch versuchen zu 
lassen, trotzt FPU. Oder halt langsamer abtasten...

Ich persönlich würde mit 20kHz messen und mit 5kHz regeln. Ist meistens 
schnell genug.

von Embedded (Gast)


Lesenswert?

Es wäre interessant zu wissen, wann welche Funktionen im Verhältnis zur 
PWM ablaufen. Dazu kann man ein Pin toggeln, welches man auf den zweiten 
Kanal hängt. Dann ist natürlich die Frage, wie die eingelesenen AD-Werte 
zur PWM passen. Wie genau sieht Euro Dummy-Control-Funktion aus? Man 
muss ja irgendwie vom 12 Bit-Wert der PWM auf 0-100% (sprich 0 - Inhalt 
Period-Register) umrechnen. Wenn das beim durchsteppen passt, sollte da 
aber eigentlich kein logischer Fehler drin sein. Aber vielleicht ist die 
AD-Messung schlecht? Ich würde da einfach mal eine Messreihe in einem 
Array festhalten und per Debugger auslesen. Oder die vorgeschlagene UART 
implementieren.

Ingo schrieb:
> Daher würde ich tatsächlich die Messung durch den PWM Interrupt triggern
> und jedes 4 mal oder so den Regler starten.

Der STM32 kann den ADC direkt über ein Timer Compare Event triggern 
(üblicherweise kurz vor dem Umkehrpunkt einer Center Aligned PWM). 
Interrupt braucht man dann nicht. Ansonsten gebe ich dir Recht. Man kann 
ein Oversampling einbauen, dafür kann man den ADC-Interrupt nehmen.

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.