STM32F1/F7 uses HAL library DMA mode to output PWM detailed explanation (output precise quantity and adjustable period and duty cycle)

1. STM32's DMA PWM principle

At the beginning, I wondered how STM32 can realize the precise number of pulse output to control the stepper motor. I didn't know that there was a DMA-PWM mode until the WS2812B lamp bead driver was made. Using DMA to output PWM can accurately control the number of pulses, and can accurately control the pulse period and duty cycle. More importantly, the use of DMA transmission does not consume CPU resources. So I searched for resources and tutorials on the Internet. Unfortunately, the online tutorials are either unclear, or the code is incomplete, or they only talk about the surface and only post the code without explaining the principle. Adhering to the ancient motto of doing it yourself, I went to read the reference manual, read the timer chapter from the DMA chapter, combined with the actual code, finally figured out some clues, and shared the problems encountered in the process.

1. Introduction to DMA

Official interpretation: DMA, the full name is: Direct Memory Access, that is, direct memory access. The DMA transmission method does not require the CPU to directly control the transmission, and there is no interruption processing method to retain the scene and restore the scene. The hardware opens up a direct data transmission path for the RAM and I/O devices, which can greatly improve the efficiency of the CPU. The following picture is a popular DMA block diagram taken from the reference manual on the Internet.

Insert picture description here
The STM32F1 series has two DMAs, with 7 channels and 5 channels (Channel) respectively. Each DMA of the F7 series has 8 data streams (Stream), and each data stream corresponds to 8 channels (Channel). Here, we slightly distinguish the expressions of the two series. The Channel in F1 should correspond to the Stream in F7. For example, the channel correspondence table of DMA1 is as follows. STM32's ADC, SPI, IIS, USART, IIC, TIM, DAC and other data transmission peripherals can all be set to DMA mode transmission. You can select the channel by looking up the table during manual configuration. Of course, if you use the Cubemx tool, it will be automatically selected Up.
Insert picture description hereWhat are the benefits of DMA transfer? For example, in serial communication, the same is to send data, using HAL_UART_Transmit() and HAL_UART_Transmit_DMA(). The former uses the normal mode, and the CPU will enter the execution function until the data transfer is completed and exit, and then execute the next instruction. The latter uses DMA to transfer. After the DMA starts the transfer, the CPU ignores it and executes other instructions directly. What does the CPU do? Only need to deal with interrupts such as DMA transfer completion, half transfer completion, transfer error, etc., or check the status of the DMA transfer by querying the register. Was it going to get up in an instant!

2. What is going on with DMA output PWM?

Using DMA to transfer data is easy to understand. Why can DMA control the number of PWM pulses and duty cycle? Here we return to the essence. When the DMA controls the PWM output, the DMA still transmits data, but it sends the comparison value, that is, the value of the capture/compare register (TIMx_CCRx). This value does not need to be explained, and The value of the auto-reload register (TIMx_ARR) determines the period and duty cycle respectively. Take a look at the explanation of the timer's DMA continuous transfer mode in the manual.

Insert picture description here
Pay attention to the yellow part, what is an update event? To recap, in the up-counting mode, an update event (overflow) will occur when the count reaches the auto-reload value, and then the count will be restarted from zero. That is to say, after each single PWM wave is over, the comparison value will be automatically set to the data transferred by DMA. Take the 1ms period set in this example as an example, set send_Buf[] = {10,20,30,…,100}, the final waveform is ten square waves with high level time of 10,20,30,…100us respectively .
It's very simple! !

3. Several functions of HAL library DMA configuration PWM

To be honest, using the HAL library is still a bit twisty. Many operations are encapsulated at various levels. You may need to call several functions in the HAL library for a few sentences of code. But this is also the general trend. Encapsulate the bottom layer. Let users focus on the application. Recently, the feeling of using Cubemx to automatically generate code is that it is so fragrant! !

Transfer the function definitions in stm32F7xx_hal_tim.h, the following are the blocking mode, interrupt mode, and DMA mode to start and stop PWM.

/* Blocking mode: Polling */
HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);
HAL_StatusTypeDef HAL_TIM_PWM_Stop(TIM_HandleTypeDef *htim, uint32_t Channel);
/* Non-Blocking mode: Interrupt */
HAL_StatusTypeDef HAL_TIM_PWM_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel);
HAL_StatusTypeDef HAL_TIM_PWM_Stop_IT(TIM_HandleTypeDef *htim, uint32_t Channel);
/* Non-Blocking mode: DMA */
HAL_StatusTypeDef HAL_TIM_PWM_Start_DMA(TIM_HandleTypeDef *htim, uint32_t Channel, uint32_t *pData, uint16_t Length);
HAL_StatusTypeDef HAL_TIM_PWM_Stop_DMA(TIM_HandleTypeDef *htim, uint32_t Channel);

The following is the declaration of the interrupt callback function.

/* Callback in non blocking modes (Interrupt and DMA) *************************/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_ErrorCallback(TIM_HandleTypeDef *htim);

Here we only pay attention to void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim);
calling this function every time the PWM output is completed. In the interrupt, we need to call to HAL_TIM_PWM_Stop_DMA(TIM_HandleTypeDef *htim, uint32_t Channel)stop the DMA transfer, otherwise it will not stop by itself.

2. STM32CubeMx configuration DMA PWM

Take the STM32F1 and F7 series boards as an example for testing. After testing, the configuration of the two is basically the same, and the result is the same, so I will take F1 as an example to explain.

As shown in the figure, to create a new project based on STM32F103ZET6, first configure the clock and set the system clock to a maximum of 72MHz.

Insert picture description here
Then set the timer, I use T2, all four channels are selected, and you can do it as needed. The frequency division coefficient is set to 71, that is, the frequency is divided by 72, the pwm frequency is 1MHz, the auto-reload value is 1000, and the cycle is 1ms.

Insert picture description here
Next, set the DMA, as shown in the figure, the four channels DMA are all selected, here CH2 and CH4 share a channel, leave it alone for the time being.

Insert picture description here
You can see that the DMA interrupt has been turned on at this time

Insert picture description here
In addition, if you use ST-Link to download the program, please note that the debug mode is set to Serial Wire in this place as shown in the figure. Otherwise, ST-Link can only download the program once.

Insert picture description here
Now that the setting is complete, just click GENERATE CODE.

3. Analysis of the waveform debugging process

Open the project, you can see the initialization function of TIM and DMA, here add code to the main function, call the HAL_TIM_PWM_Start function to output the continuous waveform normally.

HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1)

Call the __HAL_TIM_SET_COMPARE function to change the duty cycle

__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,200);

If set to 200, the high level time is 200us, and the duty cycle is 200/1000.

Because we want to use DMA, define a sending data buffer in main.c

#define NUM 21
uint32_t send_Buf[NUM] = {
    
    0};

Add the following code in the main function

for (i = 0; i < NUM; i++)
{
    
    
 	send_Buf[i] = 20 * (i + 1);
}

while (1)
{
    
    
	 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET); 
	 HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_RESET); 
	 HAL_Delay(200);
	 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET); 
	 HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_SET); 
	 HAL_Delay(200);
	 HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_3,(uint32_t*)send_Buf,NUM);
}

Add the following function

// PWM DMA 完成回调函数
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
    
    
	HAL_TIM_PWM_Stop_DMA(&htim2, TIM_CHANNEL_3);
}

This is the first part of the need to call the function in the callback function to HAL_TIM_PWM_Stop_DMAstop the PWM output.
Theoretically speaking, the expected waveform should be output as we want, that is, 21 waves with increasing duty cycle. But unfortunately my wave is like this:

Insert picture description here
Three problems:
(1) The data is only half;
(2) The period of the wave becomes twice the normal (2ms)
(3) The last data ran to the forefront.

Angry...

So I started to adjust the bug, and the first problem was discovered. Since HAL_TIM_PWM_Start_DMA(TIM_HandleTypeDef *htim, uint32_t Channel, uint32_t *pData, uint16_t Length);the send data pointer in the function points to 32 bits, my send_Buf is also defined as 32 bits, but my setting for DMA transmission is halfword 16 bits, as shown in the figure.

Insert picture description here

That is, every time the pointer is moved by one bit from uint32_t *pData, the address is offset by two bytes. This can explain the first and second problems above, because the data we want to transmit is {sendBuf[0],sendBuf[1], …,sendBuf[20]}, but the actual data transmitted is:
sendBuf[0] low 16 bits
sendBuf[0] high 16 bits (ie 0)

sendBuf[9] low 16 bits
sendBuf[9] high 16 bits (ie 0)
sendBuf[10] low 16 bits

Next, modify the transmission word width to Word (32) bits, or change the send_Buf to uint16_t type, and the test results are correct, as shown in the figure.

Insert picture description here
So remind friends:
DMA transmission bit width and the defined buffer bit width must be consistent! !
The DMA transmission bit width and the defined buffer bit width must be consistent! !
The DMA transmission bit width and the defined buffer bit width must be consistent! !

Problem 3 still exists, so try to change the last data to 0, and the
added send_Buf[NUM - 1] = 0;waveform is normal.

Insert picture description here
The reason is not clear, whether it is because of some unstable factors at the beginning and end of DMA transmission. In this case, it is enough to add one or more 0s after the normal data each time. It does not affect the use of.

So far, the DMA control PWM output is successful, and the next article uses HAL-DMA-PWM to light up the WS2812 lamp beads.

11.22 update, some friends want to have a complete routine in private messages or comments. I uploaded it to CSDN and can be downloaded for free. Don’t forget to like it!

Transmission gate: STM32F1 uses the HAL library DMA method to output PWM routines (outputs precise quantities and adjustable period and duty cycle)

Guess you like

Origin blog.csdn.net/qq_30267617/article/details/109466698