i want to create a 40KHz sine wave with a STM32 Controller and an external DAC. I created my SPI connection to the MCP4922 and sending a sine wave table to it. The init function of the SPI interface looks like this:

static void MX_SPI1_Init(void)
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_16BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.NSS = SPI_NSS_SOFT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)

At the beginning i had a sin wave table with 50 values. The MCP4922 was generating a nice wave like i expected it with 400 Hz. So i reduced the values.

uint16_t sine_wave[] =

It is still a sine wave but very edgy, the frequency increased to 4,3 KHz. The function i call to send the data to the mcp4922 looks like this.

void MCP4922_SendByte(uint16_t dac_data)
  data[1] = ((dac_data >> 8) & 0x0F) | 0x30;

  data[0] = dac_data;    

  HAL_GPIO_WritePin(GPIOA, SPI1_CS_Pin, 0);

  HAL_SPI_Transmit(&hspi1,data, 1, HAL_MAX_DELAY);

  HAL_GPIO_WritePin(GPIOA, SPI1_CS_Pin, 1);

So my question is, how can i change my code or my hardware setup to generate a 40KHz sine wave. Is it possible to increase the speed of my SPI transmission?

  • \$\begingroup\$ Why you don't use the internal DAC? \$\endgroup\$ Mar 4, 2020 at 12:32
  • \$\begingroup\$ @MarkoBuršič not all STM32 have a DAC option available. \$\endgroup\$
    – Arsenal
    Mar 4, 2020 at 15:09
  • \$\begingroup\$ For a bunch of cents more, you get a STM32 with DAC. \$\endgroup\$ Mar 4, 2020 at 15:42

2 Answers 2


Read the Wikipedia article on sampling (the article focuses on conversion of analog to digital, you perform opposite converting digital samples to set of dots comprising the wave). Then take a piece of paper (or some drawing application software on your PC) and draw the period of the sine wave with timing on the X axis. Then divide the drawn sine wave onto the samples, and decide how many samples you want to have (= how smooth your wave will be).

Then you look into the datasheet of your components - DAC and processor, and identify if they actually can produce this sampling rate (generate proper analog voltage at the output of DAC within the sampling period you identified on your drawn sine graph).

In your example: you want 40 kHz sine wave with 50 points per period. This means sine wave will appear 40000 times within the second, thus 40000*50 samples per second must be output. It equals to 2 MHz sampling rate. With 25 samples per period you will have to use 1 MHz sampling rate, but the output sine wave will be more "edgy" per your definition.

In any case, your output will hardly be the ideal sine wave, it will be more or less the ladder. You will need to use electronic filters to smooth the wave at the specific chosen frequency (you set it to 40 kHz).

  • \$\begingroup\$ I got your point. My SPI1 is attached to the APB2 Bus which has a 16 MHz clock. In my SystemClock_config function the divider looks like this RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; . If i divide by the hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; i should have a SPI1 clock of 8MHz. But when i put a logic analyzer on my SPI1 clock it transmits with 500KHz. To slow to generate higher sin frequencies \$\endgroup\$ Mar 4, 2020 at 13:43

Definitely learn sampling theory as Anonymous says.

In short, there are 2 requirements for generating a 40 kHz sinewave.

  1. A sampling frequency more than twice your desired frequency. Push your SPI interface as fast as you can go; use that as your sampling rate Fs. If it's 80 kHz or less, find a faster interface.
  2. A reconstruction filter (analog filter) that passes everything up to the highest desired frequency, and eliminates everything above Fs/2.

In the example of your "edgy" 4.3 kHz example, this reconstruction filter would eliminate the steps to leave a pure sinewave.

The closer together your desired frequency and Fs/2 are, the more difficult it gets to design and make that filter.

Filters are categorised by their "order" which denotes the number of (second order) stages they need. The order tells you how fast the attenuation increases : 6 dB/octave per order ( = 20dB/decade per order)

So if you need a sinewave with less than 0.1% distortion, you need to attenuate those steps by 60 dB. If fs/2 = 80 kHz (Fs = 160 kHz) the filter has only 1 octave to roll off : 60/6 = 10 so you need a 10th order filter (5 opamps, and some serious precision components if you want it to work well).

If you want to use a 4th order filter (24 dB/octave) for the same spec, you need 2.5 octaves. 2^2.5 = 5.65 so Fs/2 >= 5.65 * 40 kHz or Fs > 452 kHz : Fs = 500 kHz can probably be made to work.

I've over-simplified the filter sizing aspect : glossing over the 3dB loss at the highest passband frequency, and the fact that different filter characteristics can slightly improve on stopband rejection, so increase Fs a bit more if you can, or learn a LOT about filter design (chebyshev/cauer) if you want to keep Fs to these limits.

And if you only need a fixed frequency, there are bandpass filters that can achieve the same rejection more easily.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.