Custom signal generation using PWM and DMA
Introduction
The purpose of this article is to explain how to generate a custom signal, a sine wave in this case, using an STM32 Microcontroller’s DMA controller and a PWM output from a timer. Although this example uses the NUCLEO-H745ZI-Q, the same steps can be used for other STM32H7 based boards. In this article, the following are used to generate the custom signal: - Timer in PWM output mode with the signal on CH1 - DMA controller
Note, other techniques can be used to generate various types of signals such as a DAC (Digital to Analog Controller) or even GPIO bit-banging. For this example case, generating a sine wave using a Timer and DMA is the optimal method and is the one described in this article.
1. Theory
The idea is to generate a digital signal from a timer output (PWM mode) on an STM32 and use an external RC filter to convert it to an analog sine wave. For simplicity, a basic low pass filter is used as shown below for the conversion. While this article focuses on the basics of generating the digital signal, please consult other resources for details on RC filters.

We will generate a custom signal like the one in red shown below using the STM32 Timer PWM output channel:

Then using a RC filter connected to the output of Timer PWM output channel, we will get our desired Sine Wave:
We will be using the center aligned timer mode to generate a custom signal using PWM and DMA. This can be done with one method of combining the STM32H7 timer with the internal DMA feature. This method will use the DMA to transfer the continuous data of a sine wave output to the timer CCR register. The timer CCR register controls the width of the PWM duty cycle. This will produce the resultant gate drive signal at the timer output pin. When using the DMA hardware to transfer the continuous data to the timer, the Cortex-M core is free to process other application functions in parallel. We can generate any custom waveform by feeding the data of that signal to the timer CCR register via DMA. In this exercise, we will be generating a sine wave.
2. Prerequisites
NUCLEO-H745ZI-Q:

Micro USB Cable
-
The cable connects a host computer to the Nucleo board for power and debugging/programming of the STM32 on the Nucleo board.
Oscilloscope
-
This is to look at the signal from the STM32 I/Os to check the generated signals.
Software Tools
-
STM32CubeIDE

3. Steps
In this section we will go through the steps to generate a custom signal using the STM32CubeIDE and configuring the STM32H7 used on the NUCLEO-H745ZI-Q board.
-
Open STM32CubeIDE and create a project using the NUCLEO-H745ZI-Q board
-
Clock Configuration: 480/240 MHz
We are running the STM32H7 at its maximum speed: 480 MHz for the System Clock and 240 MHz for the peripheral clocks.

- Timer 4 configuration
-
Enable TIM4
-
In configuration mode, select Cortex-M7 and select PWM Generation CH1 for Channel 1:

-
Add the following configuration
In this section we select the frequency of the PWM signals that we will generate.
Please change the counter period as shown highlighted in red below.
Also configure the PWM Generation Channel 1 as shown in red below.
Note: While the PWM is configured to have an initial pulse width of one count, the DMA will be responsible for writing the values from the table to generate the desired pulse train.

- DMA configuration
We use DMA in this case, as this is the most optimal method for generating the signal accurately. In this case, the CPU is not used and can take care of other tasks.

- Generate Code
Save the STM32CubeIDE project and this action will generate the code.
- Add code for sine wave
For this example, it is a sine wave, but other data can be used to generate a sine wave with a different frequency.
If you would like to learn more about where the data for the sine wave comes from, you can find more details in the following two-part video:
Hands-On with STM32 Timers: Custom Signal Generation using PWM and DMA , Part 1 of 2 - YouTube
Hands-On with STM32 Timers: Custom Signal Generation using PWM and DMA, Part 2 of 2 - YouTube
In main.c in the CM7 project:
...
/* USER CODE BEGIN PV */
#define CCRValue_BufferSize 37
ALIGN_32BYTES (uint32_t DiscontinuousSineCCRValue_Buffer[CCRValue_BufferSize]) =
{
14999, 17603, 20128, 22498, 24640, 26488, 27988, 29093, 29770,
29998, 29770, 29093, 27988, 26488, 24640, 22498, 20128, 17603,
14999, 12394, 9869, 7499, 5357, 3509, 2009, 904, 227, 1, 227,
904, 2009, 3509, 5357, 7499, 9869, 12394, 14999
};
/* USER CODE END PV */
- Enable D & I (Data and Instruction) cache and flush D cache
The two caches in the STM32 are enabled to increase overall performance. Also, the data cache must be flushed to properly update the contents of the SRAM used by the DMA.
Add following code in main.c for the CM7 project:
/* USER CODE BEGIN 1 */ /* Enable I-Cache---------------------------------------------------------*/ SCB_EnableICache(); /* Enable D-Cache---------------------------------------------------------*/ SCB_EnableDCache(); /* USER CODE END 1 */ … /* USER CODE BEGIN Init */ /* Clean Data Cache to update the content of the SRAM to be used by the DMA */ SCB_CleanDCache_by_Addr((uint32_t *) DiscontinuousSineCCRValue_Buffer, CCRValue_BufferSize ); /* USER CODE END Init */
- Start function to transfer data from RAM to timer
Add the following code in main.c for the CM7 project:
/* USER CODE BEGIN 2 */ HAL_TIM_PWM_Start_DMA(&htim4, TIM_CHANNEL_1, DiscontinuousSineCCRValue_Buffer, CCRValue_BufferSize); /* USER CODE END 2 */
-
Compile and flash the code to your Nucleo board
-
Run the code by resetting the board using the black reset button B2 on the Nucleo board
4. Results on the Oscilloscope
Output of the custom PWM signal from TIM4 CH1 (GPIO Pin PD12) is available on the Nucleo board on connector CN10, pin #21 (see the image below).

Oscilloscope capture on PD12:

When adding the external RC filter to the PWM output, the signal is converted to a sine wave as shown in the following oscilloscope capture:

5. Links
-
AN4013: STM32 cross-series timer overview - Application note
-
NUCLEO-H745ZI-Q: NUCLEO-H745ZI-Q - STM32 Nucleo-144 development board with STM32H745ZI MCU, SMPS, supports Arduino, ST Zio and morpho connectivity - STMicroelectronics
-
STM32CubeIDE: STM32CubeIDE - Integrated Development Environment for STM32 - STMicroelectronics
-
STM32H745 reference manual: STM32H745/755 and STM32H747/757 advanced Arm®-based 32-bit MCUs - Reference manual
-
STM32H745 datasheet: Datasheet - STM32H753xI - 32-bit Arm® Cortex®-M7 400MHz MCUs, up to 2MB Flash, 1MB RAM, 46 com. and analog interfaces, crypto
Video
Below are the links to a two-part video that describes the steps covered in this article.
Hands-On with STM32 Timers: Custom Signal Generation using PWM and DMA , Part 1 of 2 - YouTube
Hands-On with STM32 Timers: Custom Signal Generation using PWM and DMA, Part 2 of 2 - YouTube
