STM32 HAL库 Timer(定时器)+DMA输出PWM底层配置过程学习

本文详细描述了如何在STM32G030芯片上使用CUBEMUX生成项目,配置TIM1定时器和DMA实现可变占空比PWM。涉及Timer初始化、DMA参数设置及代码流程,包括MX_TIM1_Init函数中的DMA配置和HAL_TIM_PWM_Start_DMA的使用。
摘要由CSDN通过智能技术生成


前言

本文使用的芯片型号是STM32G030,写本文的目前是想记录学习下Timer借助DMA生成可变占空比PWM时的底层配置过程。

一、CUBEMUX生成项目

1. Timer配置

使用TIM1,配置就只改了图上的配置,系统时钟用的16M,分频选择15(16-1),自动重装载寄存器ARR选择999(1000-1),那么生成的就是1kHz的PWM,这里为什么要减1,因为这俩是从0开始计数,想知道公式计算的可以去搜一下,介绍的有很多。Clock Source(时钟源)选择内部时钟,也就是系统时钟。通道1选择PWM。
在这里插入图片描述

这里再解释下通道选择PWM模式有几个,CH1、CH1N还有CH1和CH1N的组合。对应到下边一张图的右边,通道指Capture/Compare x register,而每个通道有两个输出口,OC1和OC1N,这俩是相反的波形,就是对应选择的选项了CH1、CH1N,选择他俩就是同时输出了。
在这里插入图片描述
在这里插入图片描述

2. DMA配置

这是DMA的配置参数,这里选择循环发送,如果只发送一次,就看不出来PWM变化了,所有我改成了循环发送。这里的数据宽度为什么要选择半字是根据TIM1的CCR寄存器定的,看下边一张图,CCR寄存器就是控制PWM占空比的,他的大小就是16位的。
在这里插入图片描述
在这里插入图片描述

二、代码流程

1.代码

代码如下:

int main(void)
{
  /* USER CODE BEGIN 1 */
  uint16_t aDutyCycleArray[9] = {100, 200, 300, 400, 600, 700, 800, 900};
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();
  
  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_TIM1_Init();
  /* USER CODE BEGIN 2 */
  HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t*)aDutyCycleArray, 9);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
    /* USER CODE END 3 */
}

2.流程

MX_GPIO_Init()MX_DMA_Init() 这两个函数就不讲了。

1) MX_TIM1_Init()

该函数大部分都和不使用DMA一样,主要分析下添加的部分,这个全局变量是添加DMA需要的。

DMA_HandleTypeDef hdma_tim1_ch1;
    /* TIM1 DMA Init */
    /* TIM1_CH1 Init */
    hdma_tim1_ch1.Instance = DMA1_Channel1;
    hdma_tim1_ch1.Init.Request = DMA_REQUEST_TIM1_CH1;
    hdma_tim1_ch1.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_tim1_ch1.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_tim1_ch1.Init.MemInc = DMA_MINC_ENABLE;
    hdma_tim1_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_tim1_ch1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_tim1_ch1.Init.Mode = DMA_CIRCULAR;
    hdma_tim1_ch1.Init.Priority = DMA_PRIORITY_LOW;
    if (HAL_DMA_Init(&hdma_tim1_ch1) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(tim_baseHandle,hdma[TIM_DMA_ID_CC1],hdma_tim1_ch1);

进入HAL_DMA_Init 函数,

1、hdma->ChannelIndex = (((uint32_t)hdma->Instance - (uint32_t)DMA1_Channel1) / ((uint32_t)DMA1_Channel2 - (uint32_t)DMA1_Channel1)) << 2U;
/* Clear PL, MSIZE, PSIZE, MINC, PINC, CIRC, DIR and MEM2MEM bits */
2CLEAR_BIT(hdma->Instance->CCR, (DMA_CCR_PL    | DMA_CCR_MSIZE  | DMA_CCR_PSIZE  | \
                                  DMA_CCR_MINC  | DMA_CCR_PINC   | DMA_CCR_CIRC   | \
                                  DMA_CCR_DIR   | DMA_CCR_MEM2MEM));
/* Set the DMA Channel configuration */
3SET_BIT(hdma->Instance->CCR, (hdma->Init.Direction           |                               \
                                hdma->Init.PeriphInc           | hdma->Init.MemInc           | \
                                hdma->Init.PeriphDataAlignment | hdma->Init.MemDataAlignment | \
                                hdma->Init.Mode                | hdma->Init.Priority));
/* Initialize parameters for DMAMUX channel :
     DMAmuxChannel, DMAmuxChannelStatus and DMAmuxChannelStatusMask
  */
4DMA_CalcDMAMUXChannelBaseAndMask(hdma);
/* Set peripheral request  to DMAMUX channel */
5、hdma->DMAmuxChannel->CCR = (hdma->Init.Request & DMAMUX_CxCR_DMAREQ_ID);
/* Clear the DMAMUX synchro overrun flag */
6、hdma->DMAmuxChannelStatus->CFR = hdma->DMAmuxChannelStatusMask;
  1. 绑定当前hdma_tim1_ch1变量用的是通道几,在配置阶段我们配置的是通道1,这款芯片总共有5个DMA通道。
  2. 第2点和第3点一起讲,就是DMA的CCR寄存器先清空再根据我们上一张图设置的参数来设置该寄存器。
  3. 总结到第2点。
  4. DMA_CalcDMAMUXChannelBaseAndMask 主要用于建立DMA和DMAMUX通道的连接,DMAMUX相当于DMA和Timer之间加的一个请求器,DMA可以帮很多模块搬东西,但怎么去与不同的模块连接就靠DMAMUX这个中间件来完成。
  5. 建立DMAMUX和Timer的连接,这就相当于,上一步把DMAMUX的一头连接到了DMA,这一步把DMAMUX的另一头连接到了Timer。
  6. 清除标志位,这个同步标志位我也不懂,如果有看懂的也可以留言教教我,哈哈哈。

再回头看看这行代码

__HAL_LINKDMA(tim_baseHandle,hdma[TIM_DMA_ID_CC1],hdma_tim1_ch1);

tim_baseHandle就是传入的TIM1 ,看下TIM1 的结构体定义,hdma 是个DMA_HandleTypeDef类型的指针数组。
在这里插入图片描述
他的宏是这样的,

#define __HAL_LINKDMA(__HANDLE__, __PPP_DMA_FIELD__, __DMA_HANDLE__)               \
                        do{                                                      \
                              (__HANDLE__)->__PPP_DMA_FIELD__ = &(__DMA_HANDLE__); \
                              (__DMA_HANDLE__).Parent = (__HANDLE__);             \
                          } while(0U)

TIM_DMA_ID_CC1这个的值是1,带入参数展开后就是,就是这个指针数组的1号位置 = hdma_tim1_ch1变量的地址。

TIM1->hdma[TIM_DMA_ID_CC1] = &hdma_tim1_ch1;
hdma_tim1_ch1.Parent = TIM1;

2)HAL_TIM_PWM_Start_DMA()

HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t*)aDutyCycleArray, 9);

这里有个疑点就是我们前边设置的搬运Memory的宽度是16位,为什么这里要转成32位呢,其实就是这个函数要求要传32位的,但搬运的时候是按照16位来搬,除非代码里用到地址自增自减,否则是不会有影响的。
进入到函数,跳过不重要的部分。

  • 这里的hdma[TIM_DMA_ID_CC1] 就等于hdma_tim1_ch1。先是设置了DMA传输的回调函数,当DMA传输到相应状态时就会触发中断,从而调用这里设置的回调函数。然后开启了DMA通道HAL_DMA_Start_IT,最后打开Timer的DMA请求使能 __HAL_TIM_ENABLE_DMA, 当Timer开启后就会通知DMA搬运数据过来。
  ...
  switch (Channel)
  {
    case TIM_CHANNEL_1:
    {
      /* Set the DMA compare callbacks */
      htim->hdma[TIM_DMA_ID_CC1]->XferCpltCallback = TIM_DMADelayPulseCplt;
      htim->hdma[TIM_DMA_ID_CC1]->XferHalfCpltCallback = TIM_DMADelayPulseHalfCplt;

      /* Set the DMA error callback */
      htim->hdma[TIM_DMA_ID_CC1]->XferErrorCallback = TIM_DMAError ;

      /* Enable the DMA channel */
      if (HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_CC1], (uint32_t)pData, (uint32_t)&htim->Instance->CCR1, Length) != HAL_OK)
      {
        /* Return error status */
        return HAL_ERROR;
      }

      /* Enable the TIM Capture/Compare 1 DMA request */
      __HAL_TIM_ENABLE_DMA(htim, TIM_DMA_CC1);
      break;
    }
    ...

进入到HAL_DMA_Start_IT,省略了一些过程,就是开启DMA连接Memory和Timer通道以及DMA中断。

/* Disable the peripheral */
    __HAL_DMA_DISABLE(hdma);
/* Configure the source, destination address and the data length & clear flags*/
    DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength);
	
	__HAL_DMA_DISABLE_IT(hdma, DMA_IT_HT);
    __HAL_DMA_ENABLE_IT(hdma, (DMA_IT_TC | DMA_IT_TE));

/* Enable the Peripheral */
    __HAL_DMA_ENABLE(hdma);
  • 返回到上一层函数,接着往Timer的DMA请求使能往后讲,开启比较通道寄存器,开启输出,最后开启Timer,开始生成PWM,并从DMA出传入数据到CCR寄存器。
/* Enable the Capture compare channel */
  TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
/* Enable the main output */
  __HAL_TIM_MOE_ENABLE(htim);
    
  __HAL_TIM_ENABLE(htim);

三、结果展示

最后给张结果展示图,我设置的CCR的值从100到900不断循环,ARR的值又为1000,所以占空比就是10%~90%不断变化。
在这里插入图片描述

  • 13
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ws2812b是一种智能彩灯,在STM32上使用HAL库进行编程时,可以利用PWMDMA控制来控制彩灯的颜色和亮度。 PWM(脉宽调制)是一种常用的控制电子设备亮度的方法,通过改变PWM信号的占空比即高电平时间和低电平时间的比例,可以控制灯光的亮度。对于ws2812b彩灯,它需要接收到一串特定的脉冲信号来控制灯光的颜色和亮度,因此我们可以利用PWM信号来模拟这个特定的脉冲信号。 在使用HAL库进行编程时,可以利用定时器PWM功能来生成脉冲信号。首先,我们需要初始化定时器PWM通道,然后设置定时器的计数周期和预分频值,以确定脉冲信号的频率。接下来,我们可以通过改变PWM通道的CCR寄存器的值来改变脉冲信号的占空比,从而控制灯光的亮度。通过反复改变CCR寄存器的值,即可实现灯光的渐变效果。 为了实现更高效的控制,可以结合使用DMA(直接存储器访问)功能。DMA可以在处理器和外设之间直接传输数据,减轻CPU的负担。对于控制彩灯来说,我们可以把存储颜色和亮度信息的数组存放在内存中,然后通过DMA传输到PWM寄存器中,从而控制彩灯。通过配置DMA通道和中断,可以实现定时更新彩灯的效果。 总之,通过利用STM32HAL库,结合PWMDMA控制,我们可以方便地对ws2812b彩灯进行编程,实现灯光的颜色和亮度控制,使其呈现出丰富多彩的效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值