cancel
Showing results for 
Search instead for 
Did you mean: 

Custom Signal generation using PWM and DMA

ST AME Support NF
ST Employee

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 most optimal method and the is the one described in this article.

0. 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.

1683.png

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

1685.png
Then using a RC filter connected to the output of Timer PWM output channel, we will get our desired Sine Wave:
1687.png

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. Pre-requisites

NUCLEO-H745ZI-Q:

1690.jpg

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

1691.png
 

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.

  1. Open STM32CubeIDE and create a project using the NUCLEO-H745ZI-Q board

  2. 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.


1692.png
 

  1. Timer 4 configuration
  •  Enable TIM4

  • In configuration mode, select Cortex-M7 and select PWM Generation CH1 for Channel 1:

1693.png
 

  • 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.

1695.png

  1. 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.


1697.png
 

  1. Generate Code

Save the STM32CubeIDE project and this action will generate the code.

  1. 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 */
  1. 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 */
  1. 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 */
  1. Compile and flash the code to your Nucleo board

  2. 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).

1698.png

Oscilloscope capture on PD12:

1700.png

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:

1702.png

5. Links

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

Comments
LJi
Associate

I have more questions about this DMA timer update operation.

  1. Can I generate the second PWM with channel 2 of the TIM4 and start them at the same time?
  2. Does your example work with Cortex-M4?
  3. If the anser is yes to above question, can I use the same timer setting with STM32F4?

Thanks

PGood.1
Associate II

A simple project on a stm32G030C8Tx

A bit of an issue with a single transfer if you set up CubeIDE for a Normal Mode in the DMA settings then the PWM is left running at the last value in your DMA memory array in this example the 

PGood1_1-1711740393536.png

 

 So if I genererate a CubeIDE project with 

Channel1  PWM Generation CH1

Prescaler 10

Counter Mode Up

Clock period 255

Internal Clock Division No Devision

Repetition counter 0

auto-reload preload 0

DMA Settings 

TIM!_CH1          DMA1 Channel 1       Memory To Peripheral         Low

DMA Request Settings

Mode Normal

Increment Address Memory and Not Peripheral

Data Width 

Periperial Word

Memory Byte

PGood1_0-1711740049388.png

 

/* USER CODE BEGIN 2 */
//TIM1->CCR1 = 4;
//HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);

uint8_t pwmData[10];

pwmData[0] = 25;
pwmData[1] = 50;
pwmData[2] = 75;
pwmData[3] = 100;
pwmData[4] = 125;
pwmData[5] = 150;
pwmData[6] = 175;
pwmData[7] = 200;
pwmData[8] = 225;
pwmData[9] = 250;

HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t *)pwmData, 10);

 

 

 

 

 

Version history
Last update:
‎2023-12-15 12:21 AM
Updated by:
Contributors