on
2021-10-06
12:26 AM
- edited on
2023-12-15
12:21 AM
by
Laurids_PETERSE
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.
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.
NUCLEO-H745ZI-Q:
The cable connects a host computer to the Nucleo board for power and debugging/programming of the STM32 on the Nucleo board.
This is to look at the signal from the STM32 I/Os to check the generated signals.
STM32CubeIDE
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.
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.
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.
Save the STM32CubeIDE project and this action will generate the code.
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 */
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 */
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
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:
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
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
I have more questions about this DMA timer update operation.
Thanks
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
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
/* 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);