Forum: Mikrocontroller und Digitale Elektronik STM32 Sin-Wave generator


von Walt N. (belayason)


Lesenswert?

Moin Leute,
ich habe mir einen Sinus Generator zur Aufgabe gemacht. Umgesetzt wird 
das auf einem STM32 NUCLEO Board L053R8. Meine Werte für den Sinus 
speichere ich in einem Array und gebe die Werte per DMA auf den DAC. Der 
Sinus hat eine Frequenz von 1.4kHz und Sp-Sp 3.3V. Soweit so gut, nun 
möchte ich aber gerne die Frequenz erhöhen, 40kHz wäre das Ziel. Als 
Trigger Source benutzt der DAC den DAC_TRIGGER_T6_TRGO vom Timer6. 
Dennoch bekomme ich durch Änderung des prescalers und der Periode keine 
Frequenz höher als 1.4kHz generiert. Habe ich die SysClock richtig 
eingestellt? Vielleicht hat ja jemand ein Tip?
1
/* Includes ------------------------------------------------------------------*/
2
#include "main.h"
3
#include "math.h"
4
5
/* Private variables ---------------------------------------------------------*/
6
DAC_HandleTypeDef hdac;
7
DMA_HandleTypeDef hdma_dac_ch1;
8
9
TIM_HandleTypeDef htim6;
10
11
/* USER CODE BEGIN PV */
12
float  pi = 3.14159265359;
13
uint16_t dacVal [1024];
14
int i;
15
16
/* USER CODE END PV */
17
18
/* Private function prototypes -----------------------------------------------*/
19
void SystemClock_Config(void);
20
static void MX_GPIO_Init(void);
21
static void MX_DMA_Init(void);
22
static void MX_DAC_Init(void);
23
static void MX_TIM6_Init(void);
24
25
int main(void)
26
{
27
  for(i=0; i<= 1023; i++)
28
   {
29
     dacVal[i]= 2048*(sin(i*2*pi/1023)+1);
30
   }     
31
32
  HAL_Init();
33
34
  /* Configure the system clock */
35
  SystemClock_Config();
36
37
  /* Initialize all configured peripherals */
38
  MX_GPIO_Init();
39
  MX_DMA_Init();
40
  MX_DAC_Init();
41
  MX_TIM6_Init();
42
43
  /* USER CODE BEGIN 2 */
44
  HAL_TIM_Base_Start(&htim6);
45
  HAL_DAC_Start(&hdac,DAC_CHANNEL_1);
46
  HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)dacVal, 1024, DAC_ALIGN_12B_R);
47
  /* USER CODE END 2 */
48
49
  while (1)
50
  {
51
52
  }
53
}
54
55
void SystemClock_Config(void)
56
{
57
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
58
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
59
60
  /**Configure the main internal regulator output voltage 
61
  */
62
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
63
  /**Initializes the CPU, AHB and APB busses clocks 
64
  */
65
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
66
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
67
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
68
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
69
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
70
  {
71
    Error_Handler();
72
  }
73
  /**Initializes the CPU, AHB and APB busses clocks 
74
  */
75
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
76
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
77
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
78
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
79
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
80
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
81
82
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
83
  {
84
    Error_Handler();
85
  }
86
}
87
88
static void MX_DAC_Init(void)
89
{
90
91
  DAC_ChannelConfTypeDef sConfig = {0};
92
93
  hdac.Instance = DAC;
94
  if (HAL_DAC_Init(&hdac) != HAL_OK)
95
  {
96
    Error_Handler();
97
  }
98
99
  /**DAC channel OUT1 config*/
100
  sConfig.DAC_Trigger = DAC_TRIGGER_T6_TRGO;
101
  sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
102
  if (HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1) != HAL_OK)
103
  {
104
    Error_Handler();
105
  }
106
}
107
108
static void MX_TIM6_Init(void)
109
{
110
  TIM_MasterConfigTypeDef sMasterConfig = {0};
111
112
  /* USER CODE END TIM6_Init 1 */
113
  htim6.Instance = TIM6;
114
  htim6.Init.Prescaler = 0;
115
  htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
116
  htim6.Init.Period = 10;
117
  htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
118
  if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
119
  {
120
    Error_Handler();
121
  }
122
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
123
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
124
  if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
125
  {
126
    Error_Handler();
127
  }
128
}
129
130
static void MX_DMA_Init(void) 
131
{
132
  /* DMA controller clock enable */
133
  __HAL_RCC_DMA1_CLK_ENABLE();
134
135
  /* DMA interrupt init */
136
  /* DMA1_Channel2_3_IRQn interrupt configuration */
137
  HAL_NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0, 0);
138
  HAL_NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
139
140
}
141
142
static void MX_GPIO_Init(void)
143
{
144
  /* GPIO Ports Clock Enable */
145
  __HAL_RCC_GPIOA_CLK_ENABLE();
146
147
}
148
149
void Error_Handler(void)
150
{
151
  /* USER CODE BEGIN Error_Handler_Debug */
152
  /* User can add his own implementation to report the HAL error return state */
153
154
  /* USER CODE END Error_Handler_Debug */
155
}

von Eric B. (beric)


Lesenswert?

Walt N. schrieb:
> nun
> möchte ich aber gerne die Frequenz erhöhen, 40kHz wäre das Ziel.

Kleinere Tabelle benutzen. Mit 25 Stellen statt 1024 wärst du schon fast 
am Ziel.

von Georg (Gast)


Lesenswert?

Muss das unbedingt ein Controller sein? Für eine Sinusausgabe braucht 
man einen Taktgenerator, einen Adresszähler, einen Speicher für die 
Amplitudenwerte und einen DAC, das kann man für fast beliebige 
Frequenzen aus wenigen Standard-ICs konstruieren. Nur der Lerneffekt ist 
dann natürlich ein anderer.

Georg

von required (Gast)


Lesenswert?

>das kann man für fast beliebige
>Frequenzen aus wenigen Standard-ICs konstruieren.

Warum sollte man das wollen, wenn ein einziges Standard-IC, nämlich ein 
µC es auch tun würde...

von Georg (Gast)


Lesenswert?

required schrieb:
> Warum sollte man das wollen, wenn ein einziges Standard-IC, nämlich ein
> µC es auch tun würde...

Eben weil man damit sehr viel höhere Frequenzen erreichen kann. Thread 
gelesen?

Georg

von Guest (Gast)


Lesenswert?

Georg schrieb:
> Eben weil man damit sehr viel höhere Frequenzen erreichen kann. Thread
> gelesen?

Also der STM schafft ja wohl locker mehr als 1.4kHz .....

von LTC1043 (Gast)


Lesenswert?

Schau mal beim Cleveren Japaner ;-)

http://elm-chan.org/junk/mdds_ipol/report.html

Ist für ein STM32F3, lässt sich aber Problemlos Portieren.

Cheers

von Walt N. (belayason)


Lesenswert?

Eric B. schrieb:
> Kleinere Tabelle benutzen. Mit 25 Stellen statt 1024 wärst du schon fast
> am Ziel.

Das hat funktioniert, danke! Wenn ich die anzahl der Werte nicht 
verringern möchte, ist der andere weg doch die Systemclock zu erhöhen, 
oder? Kann ich das durch hinzufügen eines externen oscillators 
erreichen?

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Für 1024 Stufen und 1.4kHz müsste dein Timer auf knapp 700ns Interrupts 
generieren :-)
1.4k * 1024 = ~1.5MHz -> 698ns

Das wird eng, daher: Stufen runter.
Du könntest das ganze dynamisch gestalten, alle x kHz nimmst du nur noch 
die Hälfte der Werte des arrays, also counter +1, +2, +4, +8, ...
Ggf. Mittelwert aus den übersprungenen Werten bilden (decimation).

: Bearbeitet durch User
von Walt N. (belayason)


Lesenswert?

Ich komme meinem Ziel immer näher. Ich habe die erforderliche Schwingung 
in der richtigen Frequenz. Meinen DAC starte ich im DMA modus.
1
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)dacVal, 25, DAC_ALIGN_12B_R);
Die 25 Werte entsprechen der positiven Halbwelle meines Sinus Signal. 
Die Frequenz ist bei 40 kHz, also genau so wie ich sie benötige. Der 
oben genannte befehl übergibt einmal die 25 Werte an meinen DAC, wenn 
ich das richtig verstanden habe. Nun möchte ich aber bestimmen wie oft 
ich diese positive Halbwelle ausgebe. Funktioniert das im DMA modus?
1
      HAL_Delay(20);
2
      HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)dacCosVal, 25, DAC_ALIGN_12B_R);
3
      HAL_DAC_Stop_DMA(&hdac, DAC1_CHANNEL_1);
4
      HAL_Delay(20);
Das hier endet in einem undefinierbaren chaos :/
1
    for(i=0;i<=25;i++)
2
    {
3
      HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,dacVal[i]);
4
  
5
    }
Und diese Version dauert 4 mal länger-

: Bearbeitet durch User
von Bauform B. (bauformb)


Angehängte Dateien:

Lesenswert?

Guest schrieb:
> Also der STM schafft ja wohl locker mehr als 1.4kHz .....

Aber nicht mit so hoher zeitlicher Auflösung.

Walt N. schrieb:
> Wenn ich die anzahl der Werte nicht verringern möchte, ist der
> andere weg doch die Systemclock zu erhöhen, oder?

Erstmal müsstest du nur den Takt für den DAC erhöhen. Viel mehr als 1MHz 
schafft der Analogteil aber nicht, siehe Bild. Also reichen die 16MHz 
aus dem HSI locker.

> Kann ich das durch hinzufügen eines externen oscillators erreichen?

Das lohnt sich nicht, selbst wenn der DAC mehr könnte. Es ist egal, ob 
der Takt extern oder intern per PLL und HSI erzeugt wird, Systemtakt 
bleibt Systemtakt. Einen externen Takt brauchst du (nur), wenn der HSI 
nicht stabil genug ist, z.B. für CAN oder weil es bei -40°C 
funktionieren muss. Oder wenn du unbedingt eine krumme Frequenz 
brauchst.

von Marcus H. (Firma: www.harerod.de) (lungfish) Benutzerseite


Lesenswert?

Bauform B. schrieb:
...
> Erstmal müsstest du nur den Takt für den DAC erhöhen. Viel mehr als 1MHz
> schafft der Analogteil aber nicht, siehe Bild. Also reichen die 16MHz
> aus dem HSI locker.
Der verlinkte Ausschnitt aus dem Datenblatt zeigt die Crux des 
STM32-DAC:
- full-scale Sprungantworten brauchen bis zu 12µs für Einschwingen auf 1 
LSB
- eine Änderung um 1 LSB geht in 1µs

Das sind einige der Limitierungen dieser eingebauten Peripherie (für 
weitere Überraschungen das komplette Kapitel genau lesen, siehe auch 
min/max-Werte).

Schon bei moderaten Anforderungen ist mit einem externen Wandler 
deutlich besser bedient.

von Walt N. (belayason)


Lesenswert?

Marcus H. schrieb:
> Schon bei moderaten Anforderungen ist mit einem externen Wandler
> deutlich besser bedient.

Ich habe nun einen externen Wandler angeschlossen. Per SPI sende ich 
meine Daten an den mcp4922. Funktioniert einwandfrei! Meine SPI 
Schnittstelle habe ich wie folgt initialisiert:
1
static void MX_SPI1_Init(void)
2
{
3
  hspi1.Instance = SPI1;
4
  hspi1.Init.Mode = SPI_MODE_MASTER;
5
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
6
  hspi1.Init.DataSize = SPI_DATASIZE_16BIT;
7
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
8
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
9
  hspi1.Init.NSS = SPI_NSS_SOFT;
10
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
11
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
12
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
13
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
14
  hspi1.Init.CRCPolynomial = 10;
15
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
16
  {
17
    Error_Handler();
18
  }
19
}

Die Wertetabelle für den Sinus habe ich so aufgestellt:
1
uint16_t sine_wave[] =
2
{
3
0x800,0xcb3,0xf9b,0xf9b,0xcb3,0x800,0x34c,0x64,
4
0x64,0x34c,0x800,
5
};

Und die Funktion für die Übertragung an den mcp4922 ist diese hier:
1
void MCP4922_SendByte(uint16_t dac_data)
2
{
3
  data[1] = ((dac_data >> 8) & 0x0F) | 0x30;
4
5
  data[0] = dac_data;    
6
  
7
  HAL_GPIO_WritePin(GPIOA, SPI1_CS_Pin, 0);
8
  
9
  HAL_SPI_Transmit(&hspi1,data, 1, HAL_MAX_DELAY);
10
  
11
  HAL_GPIO_WritePin(GPIOA, SPI1_CS_Pin, 1);
12
}
Das Problem besteht immer noch darin dass ich den Sinus mit 40KHz 
generieren möchte. Mit dieser kleinen Wertetabelle komme ich auf einen 
recht kantigen Sinus von 4,2KHz. Bekomme ich die SPI übertragung noch 
schneller hin? Kann ich vielleicht dieses
1
HAL_MAX_DELAY
 noch ändern um etwas an geschwindigkeit zu gewinnen? Bin über jeden 
Tipp und Hinweis dankbar!

von Marcus H. (Firma: www.harerod.de) (lungfish) Benutzerseite


Lesenswert?

Walt N. schrieb:
> Marcus H. schrieb:
>> Schon bei moderaten Anforderungen ist mit einem externen Wandler
>> deutlich besser bedient.
>
> Ich habe nun einen externen Wandler angeschlossen. Per SPI sende ich
> meine Daten an den mcp4922. Funktioniert einwandfrei!
...
Freut mich, Danke für die Rückmeldung.

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.