学习STM32 HAL库中Timer定时器和DMA输出PWM底层配置过程

文章目录

  • 前言
  • 一、CUBEMUX生成项目
  • 1. Timer配置
  • 2. DMA配置
  • 二、代码流程
  • 1.代码
  • 2.流程
  • 1) MX_TIM1_Init()
  • 2)HAL_TIM_PWM_Start_DMA()
  • 三、结果展示

  • 前言

    本文使用的芯片型号是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 */
    2、CLEAR_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 */
    3、SET_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
      */
    4、DMA_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%不断变化。

    作者:小小豆芽菜丶

    物联沃分享整理
    物联沃-IOTWORD物联网 » 学习STM32 HAL库中Timer定时器和DMA输出PWM底层配置过程

    发表评论