Forum: Mikrocontroller und Digitale Elektronik STM32L1 - 400Hz gleichger. Sinus bei 16MHz erzeugen


von Niklas B. (niklas90)


Lesenswert?

Hallo,

in einer Schaltung soll zwecks anspruchsvoller Leistungsregelung ein 
STM32L1 zum Einsatz kommen. Der Stromverbrauch soll dabei möglichst 
niedrig sein. Es sollen in Echtzeit mehrere logische und ein analoger 
Pegel aufgenommen werden. Aus diesen Informationen wird dann ein neuer 
Pegel berechnet, welcher mit einem gleichgerichteten Sinussignal bei 
400Hz ausgegeben wird.
Wir haben bereits eine Steuerung auf rein analoger Basis für die 
Schaltung entworfen, aber die ist halt nur in gewissen Grenzen/Maßen 
funktionell.



Ich habe nun das Problem, dass der L1 irgendwie rumzickt...

Die Initialisierung der Clock(s) bereitet mir Schwierigkeiten - z.b. ist 
die HSI bei mir nur 16MHz - zumindest behauptet dass 
RCC_GetClocksFreq(&...). Andererseits kommt ein sehr komischer Fehler - 
No source available for "0x......" - wenn ich versuche die PLL mit der 
HSI zu füttern. Voltage scaling range ist 1, also beste Performance.

Das ich (noch) keinen externen Quarz in den freien Platz auf dem 
Discovery eingelötet habe, kann ich auch nicht die HSE benutzen, 
allerdings ist auch Genauigkeit weniger das Problem, das Signal soll bei 
jeder Halbwelle mit Hilfe der logischen Pegel neu synchronisiert werden.

Im Moment wird noch nichts verrechnet oder gesteuert, ich probiere nur 
ein vernünftiges, gleichgerichtetes Sinussignal zu erzeugen.
Ich habe zwei Ansätze ausprobiert: DAC und PWM mit einfachem RC-Filter.

*!!!*
Beide Methoden haben das Problem, dass bei 400Hz die Sinushalbwellen 
nicht mehr symmetrisch aussehen und das Signal einen Offset (ca.300mV) 
kriegt.
*!!!*

Beim PWM liegt dass an dem RC-Filter, beim DAC an dem Puffer des L1. 
Letzteren kann man per Software disablen, nur dann sieht das Signal nach 
gar nix mehr aus, völlig unbrauchbar, riesen Offset und kaum Amplitude. 
Wieso? Der Ausgang hängt an einem Oszi mit 1MOhm Eingang. Ein Widerstand 
mit 10kOhm parallel bringt kaum (eig gar nichts) etwas.

Wenn man nach der Signalform ausgeht, gewinnt der DAC, die Unsymmetrie 
ist recht klein im Vergleich zur PWM, außerdem ist die Signalform 
deutlich glatter.
Das Signal der PWM könnte imho gewinnen wenn man die PWM schneller 
machen könnte, aber irgendwie ist die schon am Anschlag (ich komm mit 
den Clocks nicht wirklich zurecht) und die Auflösung ist schon so 
niedrig wie möglich gehalten um noch vernünftige Abstufungen zu 
bekommen.



Hier mal der Quellcode bis jetzt:
1
// includes
2
#include <stddef.h>
3
#include <stdlib.h>
4
#include "stm32l1xx.h"
5
#include "halfSinus.h"
6
7
// defines
8
#define VREF 2920 // VREF+ (mV), nearly VDD
9
10
// variables
11
volatile uint16_t amplitude;
12
13
// function prototypes
14
void DAC_Voltage(uint8_t channel, uint16_t mV);
15
uint16_t ADC_Voltage(uint8_t channel);
16
void InitPeriph();
17
void Delay(uint32_t c) {
18
  while(c--);
19
}
20
21
// main program
22
int main() {
23
24
   SystemInit();
25
26
   //PWR_VoltageScalingConfig(PWR_VoltageScaling_Range1); // selects best performance; also set in SystemInit()
27
28
   // we can't use HSE because no quarz soldered in place
29
   /*RCC_HSEConfig(RCC_HSE_ON);
30
   while(!RCC_GetFlagStatus(RCC_FLAG_HSERDY));
31
   RCC_PLLConfig(RCC_PLLSource_HSE, RCC_PLLMul_12, RCC_PLLDiv_3);
32
   RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
33
   while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)RCC_CFGR_SWS_PLL);*/
34
35
   // use HSI instead
36
   RCC_HSICmd(ENABLE);
37
   while(!RCC_GetFlagStatus(RCC_FLAG_HSIRDY));
38
   // HSI can't be used as source for PLL for some reason
39
   RCC_SYSCLKConfig(RCC_SYSCLKSource_HSI);
40
   while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)RCC_CFGR_SWS_HSI);
41
42
   RCC_ClocksTypeDef clock;
43
   RCC_GetClocksFreq(&clock); // get current clockspeed -> 16MHz it says
44
45
   InitPeriph();
46
47
   amplitude = 2000;
48
49
   while(1) {
50
     // just play around with ADC: light up green LED when swiping on touchpad
51
     if (ADC_Voltage(14) > 1100) {
52
       GPIO_WriteBit(GPIOB, GPIO_Pin_7, Bit_SET);
53
     } else {
54
       GPIO_WriteBit(GPIOB, GPIO_Pin_7, Bit_RESET);
55
     }
56
     Delay(1000);
57
     DAC_Voltage(2, ADC_Voltage(2)); // digitalize input voltage and output on other pin, useful for calibrating VREF
58
     GPIO_ToggleBits(GPIOB, GPIO_Pin_6);
59
   }
60
}
61
62
// timer interrupt handler
63
void TIM4_IRQHandler() {
64
  static uint8_t i = 0;
65
66
  if (TIM_GetITStatus(TIM4, TIM_IT_Update)) { // period overflow
67
    // clear interrupt trigger
68
    TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
69
70
    TIM3->CCR3 = halfSin[i]*511/4095;
71
    //TIM9->CCR1 = halfSin[i]*511/4095;
72
    DAC_SetChannel1Data(DAC_Align_12b_R, halfSin[i]*amplitude/VREF);
73
    if ((i+=2) >= 99) i = 0;
74
  }
75
}
76
77
78
// functions
79
void DAC_Voltage(uint8_t channel, uint16_t mV) {
80
  if (channel == 1) DAC_SetChannel1Data(DAC_Align_12b_R, mV*4059/VREF);
81
  if (channel == 2) DAC_SetChannel2Data(DAC_Align_12b_R, mV*4059/VREF);
82
}
83
84
uint16_t ADC_Voltage(uint8_t channel) {
85
  ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_4Cycles);
86
  ADC_SoftwareStartConv(ADC1);
87
  while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
88
  return ADC_GetConversionValue(ADC1)*VREF/4059;
89
}
90
91
void InitPeriph() {
92
  // turn on clock for LEDs
93
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
94
  // initialize LEDs on board
95
  GPIO_InitTypeDef gpioInitStruct;
96
  gpioInitStruct.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_6; // green led: PB7 , blue led: PB6
97
  gpioInitStruct.GPIO_Mode = GPIO_Mode_OUT;
98
  gpioInitStruct.GPIO_OType = GPIO_OType_PP;
99
  gpioInitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
100
  gpioInitStruct.GPIO_Speed = GPIO_Speed_2MHz;
101
  GPIO_Init(GPIOB, &gpioInitStruct);
102
103
  // turn on clock for DAC
104
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
105
  // initialize DAC
106
  DAC_InitTypeDef dacInitStruct;
107
  dacInitStruct.DAC_Trigger = DAC_Trigger_None;
108
  dacInitStruct.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
109
  dacInitStruct.DAC_WaveGeneration = DAC_WaveGeneration_None;
110
  // initialize both DAC channels with structure settings above
111
  DAC_Init(DAC_Channel_1, &dacInitStruct);
112
  DAC_Init(DAC_Channel_2, &dacInitStruct);
113
  // enable both DAC channels
114
  DAC_Cmd(DAC_Channel_1, ENABLE);
115
  DAC_Cmd(DAC_Channel_2, ENABLE);
116
117
  // turn on clock for ADC1
118
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
119
  /* turn on highspeed internal clock
120
  RCC_HSICmd(ENABLE);
121
  while(!RCC_GetFlagStatus(RCC_FLAG_HSIRDY)); // wait until done
122
  // initialize common (ADC analog clock prescaler) - wtf is this?
123
  ADC_CommonInitTypeDef adcCommonInitStruct;
124
  adcCommonInitStruct.ADC_Prescaler = ADC_Prescaler_Div1;
125
  ADC_CommonInit(&adcCommonInitStruct);*/
126
  // initialize ADC
127
  ADC_InitTypeDef adcInitStruct;
128
  adcInitStruct.ADC_ScanConvMode = DISABLE;
129
  adcInitStruct.ADC_ContinuousConvMode = DISABLE;
130
  adcInitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConvEdge_None;
131
  adcInitStruct.ADC_DataAlign = ADC_DataAlign_Right;
132
  adcInitStruct.ADC_NbrOfConversion = 1;
133
  ADC_Init(ADC1, &adcInitStruct);
134
  // enable ADC1
135
  ADC_Cmd(ADC1, ENABLE);
136
  while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_ADONS));
137
138
  // turn on clock for timer4
139
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
140
  // initialize timer
141
  TIM_TimeBaseInitTypeDef timInitStruct;
142
  timInitStruct.TIM_Prescaler = 1;
143
  timInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
144
  timInitStruct.TIM_Period = 200;
145
  timInitStruct.TIM_ClockDivision = 0;
146
  TIM_TimeBaseInit(TIM4, &timInitStruct);
147
  // initialize interrupts
148
  NVIC_InitTypeDef intInitStruct;
149
  intInitStruct.NVIC_IRQChannel = TIM4_IRQn;
150
  intInitStruct.NVIC_IRQChannelCmd = ENABLE;
151
  intInitStruct.NVIC_IRQChannelPreemptionPriority = 0x0F;
152
  intInitStruct.NVIC_IRQChannelSubPriority = 0x0F;
153
  NVIC_Init(&intInitStruct);
154
  // enable timer interrupt
155
  TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
156
  TIM_Cmd(TIM4, ENABLE);
157
158
  // turn on clock for timer3
159
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
160
  // initialize timer
161
  timInitStruct.TIM_Prescaler = 1;
162
  timInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
163
  timInitStruct.TIM_Period = 511;
164
  timInitStruct.TIM_ClockDivision = 0;
165
  TIM_TimeBaseInit(TIM3, &timInitStruct);
166
  // initialize pwm
167
  TIM_OCInitTypeDef ocInitStruct;
168
  ocInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
169
  ocInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
170
  ocInitStruct.TIM_OutputState = TIM_OutputState_Enable;
171
  ocInitStruct.TIM_Pulse = 0;
172
  TIM_OC3Init(TIM3, &ocInitStruct);
173
  TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);
174
  TIM_Cmd(TIM3, ENABLE);
175
  // configure pwm pin
176
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);
177
  gpioInitStruct.GPIO_Pin = GPIO_Pin_8;
178
  gpioInitStruct.GPIO_Mode = GPIO_Mode_AF;
179
  gpioInitStruct.GPIO_OType = GPIO_OType_PP;
180
  gpioInitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
181
  gpioInitStruct.GPIO_Speed = GPIO_Speed_2MHz;
182
  GPIO_Init(GPIOC, &gpioInitStruct);
183
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource8, GPIO_AF_TIM3);
184
185
  /* turn on clock for timer9
186
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM9, ENABLE);
187
  // initialize timer
188
  timInitStruct.TIM_Prescaler = 1;
189
  timInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
190
  timInitStruct.TIM_Period = 511;
191
  timInitStruct.TIM_ClockDivision = 0;
192
  TIM_TimeBaseInit(TIM9, &timInitStruct);
193
  // initialize pwm
194
  TIM_OCInitTypeDef ocInitStruct;
195
  ocInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
196
  ocInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
197
  ocInitStruct.TIM_OutputState = TIM_OutputState_Enable;
198
  ocInitStruct.TIM_Pulse = 0;
199
  TIM_OC1Init(TIM9, &ocInitStruct);
200
  TIM_OC1PreloadConfig(TIM9, TIM_OCPreload_Enable);
201
  TIM_Cmd(TIM9, ENABLE);
202
  // configure pwm pin
203
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
204
  gpioInitStruct.GPIO_Pin = GPIO_Pin_13;
205
  gpioInitStruct.GPIO_Mode = GPIO_Mode_AF;
206
  gpioInitStruct.GPIO_OType = GPIO_OType_PP;
207
  gpioInitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
208
  gpioInitStruct.GPIO_Speed = GPIO_Speed_40MHz;
209
  GPIO_Init(GPIOB, &gpioInitStruct);
210
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_TIM9);*/
211
}



In halfSin[] sind 100 in Excel erstellte Werte einer Sinushalbwelle (ich 
weiß, eine Viertelwelle reicht, aber so ist es übersichtlicher), um 
jedoch später noch genug Zeit für das Einlesen und Verarbeiten der 
anderen Pegel zu haben, lese ich nur jeden 2ten Wert aus, auf dem Oszi 
sieht man die Stufen kaum.

Um das PWM Signal zu filtern, nehme ich einen einfachen Spannungsteiler: 
"oben" 1kOhm, "unten" 100nF, Messkopf an Mitte.


Kennt ihr bessere, elegantere Möglichkeiten, ein 400Hz Sinus zu 
erzeugen, oder linear in der Amplitude zu modulieren (den 
gleichgerichtetet Sinus könnte man ja auch über Spannungsteiler mit 
einem Transistor runterziehen, welcher mit einem DC Signal gespeist 
wird, nur die Kurvenform leidet doch darunter, oder?)? Oder wisst ihr, 
wie man den ollen Puffer des DAC fixt?

von Jim M. (turboj)


Lesenswert?

Du hast ein paar Mal 4059 statt 4095 in obigem Code.

Delay() ignoriert je nach Implementation eventuell die Zeit, die in 
Interrupts zwischendurch verbraten wurde.

Dein Timer 4 läuft relativ schell - 200 Takte hat man schnell mal auch 
im Interrupt verbraten. Sind beim Compiler die Optimierungen an?

Quellcode bitte das nächste Mal als .c Datei anhängen, wenns nicht nur 
eine Handvoll Zeilen ist.

: Bearbeitet durch User
von Niklas B. (niklas90)


Lesenswert?

Danke für die Hinweise.


Klar, es muss natürlich immer 4095 sein, da hatte ich mich wohl vertippt 
:).

Das Delay() kommt später noch weg, das war nur da damit man das Leuchten 
der grünen LED besser wahrnimmt wenn man das Touchpad berührt, das ist 
alles nur zum Testen ob man gleichzeitig das 400Hz Signal im 
Timerinterrupt erzeugen kann und Pegel einlesen und verarbeiten kann. 
Das wird später natürlich noch viel komplexer.

Das bringt mich zu dem Punkt mit dem Timer 4: Der µC hat 200 Takte Zeit 
andere Berechnungen vorzunehmen und Pegel einzulesen wenn gerade nicht 
der Sinus erzeugt wird, ich denke dass ist nicht gerade viel, im 
Gegenteil, ich hatte Angst dass man nicht hinterherkommt und deshalb 
sogar die Period von 100 auf 200 gesetzt und nur jeden 2ten Wert des 
Arrays ausgegeben.

Mich beschleicht nur das Gefühl, dass der µC gar nicht mit 16MHz rennt, 
sondern nur mit 8: Nehmen wir an, er liefe mit 8MHz, und ich würde jeden 
Wert aus dem Array auslesen, also 200 Werte für eine gleichgerichtete 
Sinusperiode, also 2 Halbwellen a 100 Werte und ich würde die Period des 
Timer 4 auf 100 stellen, dann komme ich auf 8000000Hz/100/200 = 400Hz, 
also genau die gewünschte Frequenz.

Warum läuft der µC mit 8MHz, obwohl ich die HSI angeschaltet habe? 
Eigentlich sollte die doch mit 32MHz laufen, RCC_GetClocksFreq(&clock); 
spuckt mir 16MHz aus, was mache ich falsch dass er mit 8MHz läuft?


Mit dem C-Code hast du recht, nur manche haben wahrscheinlich keine Lust 
erst die Datei zu laden und in ihrem Editor zu öffnen, eine Art 
Spoiler-Funktion im Forum zum auf-und zuklappen wäre nett.

von Jim M. (turboj)


Lesenswert?

Es gibt eine Preview Funktion für C Code hier im Forum. Einfach die 
Datei mit Endung.c hochladen...

: Bearbeitet durch User
von Niklas B. (niklas90)


Lesenswert?

Ich habe jetzt den Fehler für die Kurvenform gefunden. Ich gebe ja das 
Signal an PA4 aus, auf dem Discovery Board ist dieser Pin mit einem 1µF 
verbunden, ich hab die Brücke zu ihm rausgelötet, jetzt sieht das Signal 
wunderbar aus.

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.