STM32學習及開發筆記八:採用主從計時器實現精確脈衝輸出

脈衝信號用於設備控制是非常常見的,但在一些情況下,我們希望精確的控制脈衝的數量以實現對運動的精確控制。實現的方式也許有多種多樣,但使用計時器來實現此類操作是人們比較容易想到的。

1、原理概述

我們知道在STM32平臺上,使用計時器來實現PWM操作是非常常見的用法。使用的是單一計時器,事實上通過主從兩個計時器配合我們也可通過生成PWM波的方式精確控制輸出脈衝的數量。接下來我們就來簡單瞭解一下使用主從計時器實現精確數量脈衝輸出的原理。

對於STM32平臺一般都有TIM1和TIM8兩個高級定時器和TIM2、TIM3、TIM、TIM5等幾個通用定時器。STM32的這些定時器可以通過另外一個定時器的某一個條件被觸發而啓動。這裏所謂某一個條件可以是定時到時、定時器超時、比較成功等各種條件。這種通過一個定時器觸發另一個定時器的工作方式稱爲定時器的同步,發出觸發信號的定時器工作於主模式,接受觸發信號而啓動的定時器工作於從模式。這些個計時器都可用作從計時器,但作爲主計時器則是對應不同的觸發源,它們的主從關係必須遵循設定不可隨意配置。具體的配置關係如下:

當然要實現精確控制脈衝輸出,就需要按照上述列表中的要求實現主從計時器的配置。對於主計時器來說,要將輸出配置爲PWM輸出,並將觸發輸出的主從模式啓用。而對於從計時器來說,需要啓用從模式,並設爲門控方式,觸發源則根據上述表中的描述來選擇。

可是爲什麼主從計時器就能實現精確數量的脈衝輸出呢?我們藉助下面的簡單圖示來說明這個問題。

首先按前面所述的主從計時器要求配置好主從計時器,這是最基本的要求。主計時器負責設置脈衝輸出的頻率以及輸出脈衝,從計數器所控制輸出的脈衝數。具體過程是這樣的,主進程啓動主從計時器,從計時器通過主計時器輸出的觸發信號開始脈衝計數,當達到指定的計數值後,產生中斷停止主計時器輸出,直到主進程再次開啓這一過程。

2、系統設計

我們已經瞭解了通過主從計時器實現精確數量脈衝輸出的基本原理。那究竟如何實際做呢?接下來我們就設計一個簡單的系統實現它。

在這一系統中,我是使用STM32F407作爲實現平臺,以TIM1作爲主計時器,TIM4作爲從計時器,同時輸出四路脈衝信號。四路的頻率是相同的,但每一路的輸出數量是可以設定的。具體的操作結構如下圖所示:

主進程輪詢控制計時器TIM1和TIM4工作,而TIM1主計時器給TIM4從計時器輸出觸發信號,而從計時器到達指定脈衝數後輸出中斷信號控制TIM1的輸出通道停止。我們人爲規定TIM4的通道1、2、3、4與TIM1的輸出通道1、2、3、4相對應。

3、代碼實現

我們已經說明了使用主從計時器實現精確輸出脈衝數的原理,也設計了我們的我們想要實現的系統結構,接下來我們實現這一系統。

3.1、主計時器的配置

首先我們來看一看主計時器的配置,具體代碼如下:

/*TIM1初始化配置*/
static void TIM1_Init_Configuration(uint32_t period)
{
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 1;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = (period-1);
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = (period/2);
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)
  {
    Error_Handler();
  }
  sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
  sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
  sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
  sBreakDeadTimeConfig.DeadTime = 0;
  sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
  sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
  sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
  if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
  {
    Error_Handler();
  }

  HAL_TIM_MspPostInit(&htim1);
}

3.2、從計時器的配置

接着我們再來看一看從計時器的配置,具體代碼如下:

/*TIM4初始化配置*/
static void TIM4_Init_Configuration(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_SlaveConfigTypeDef sSlaveConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim4.Instance = TIM4;
  htim4.Init.Prescaler = 0;
  htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim4.Init.Period = 0xFFFF;
  htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sSlaveConfig.SlaveMode = TIM_SLAVEMODE_GATED;
  sSlaveConfig.InputTrigger = TIM_TS_ITR0;
  if (HAL_TIM_SlaveConfigSynchronization(&htim4, &sSlaveConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

3.3、主輪詢函數實現

主輪詢函數控制着主從計時器的啓動,是實現脈衝輸出的控制者,包括設置脈衝數並開啓從計數器的計數和中斷以及啓動主計時器的輸出。具體代碼如下:

/*實現通訊數據的處理*/

void HgraDataProcess(void)
{
  TIM_SET_CAPTUREPOLARITY(&htim1,TIM_CHANNEL_1,TIM_ICPOLARITY_RISING);  // 捕獲比較1中斷使能
  TIM_SET_CAPTUREPOLARITY(&htim1,TIM_CHANNEL_2,TIM_ICPOLARITY_RISING);  // 捕獲比較2中斷使能
  TIM_SET_CAPTUREPOLARITY(&htim1,TIM_CHANNEL_3,TIM_ICPOLARITY_RISING);  // 捕獲比較3中斷使能
  TIM_SET_CAPTUREPOLARITY(&htim1,TIM_CHANNEL_4,TIM_ICPOLARITY_RISING);  // 捕獲比較4中斷使能
 
  __HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_1,6400);     // 輸入通道1的捕獲比較值CCR1
  __HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_2,6400);     // 輸入通道2的捕獲比較值CCR2
  __HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_3,6400);     // 輸入通道3的捕獲比較值CCR3
  __HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_4,6400);     // 輸入通道4的捕獲比較值CCR4

  HAL_TIM_OC_Start_IT(&htim4,TIM_CHANNEL_1);    //開啓定時器4通道1的輸入捕獲中斷
  HAL_TIM_OC_Start_IT(&htim4,TIM_CHANNEL_2);    //開啓定時器4通道2的輸入捕獲中斷
  HAL_TIM_OC_Start_IT(&htim4,TIM_CHANNEL_3);    //開啓定時器4通道3的輸入捕獲中斷
  HAL_TIM_OC_Start_IT(&htim4,TIM_CHANNEL_4);    //開啓定時器4通道4的輸入捕獲中斷

  HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_1);  //開啓定時器1通道1的PWM輸出中斷        
  HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_2);  //開啓定時器1通道2的PWM輸出中斷
  HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_3);  //開啓定時器1通道3的PWM輸出中斷
  HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_4);  //開啓定時器1通道4的PWM輸出中斷
}

3.4、中斷處理函數的實現

從計時器產生中斷後,會根據不同的中斷調用不同的中斷處理函數,這些回調函數是需要我們實現的,在這裏要實現主計時器PWM輸出的停止以及中斷標誌的復位等處理。具體實現代碼如下:

/*PWM中斷輪詢回調函數*/
static void TIM1_PWM_PulseFinished(TIM_HandleTypeDef *htim)
{
  if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC1) != RESET)                         //判斷是否生成中斷標誌位SR
  {
    if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC1) !=RESET)              //定時器中斷使能是否開啓
    {
      __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_CC1);                   //清除中斷標誌位SR
      if(HAL_TIM_PWM_Stop_IT(&htim1, TIM_CHANNEL_1)==HAL_OK)         //關閉定時器1的通道1的PWM輸出
      {
        HAL_TIM_OC_Stop_IT(&htim4,TIM_CHANNEL_1) ;                   //關閉定時器4的通道1的輸入中斷捕獲
        flagStop[0] = 1;                                                //關閉標誌置1
      }
    }
  }                                                                          //下面的通道2同理如此
  if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC2) != RESET)
  {
    if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC2) !=RESET)
    {
      __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_CC2);                       //清除標誌位
     
      if(HAL_TIM_PWM_Stop_IT(&htim1, TIM_CHANNEL_2)==HAL_OK)
      {    a
        HAL_TIM_OC_Stop_IT(&htim4,TIM_CHANNEL_2) ;   
        flagStop[1] = 1;           
      }
    }
  }
  if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC3) != RESET)
  {
    if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC3) !=RESET)
    {
      __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_CC3);                       //清除標誌位
     
      if(HAL_TIM_PWM_Stop_IT(&htim1, TIM_CHANNEL_3)==HAL_OK)
      {    
        HAL_TIM_OC_Stop_IT(&htim4,TIM_CHANNEL_3) ;   
        flagStop[2] = 1;           
      }
    }
  }
  if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC4) != RESET)
  {
    if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC4) !=RESET)
    {
      __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_CC4);                       //清除標誌位
     
      if(HAL_TIM_PWM_Stop_IT(&htim1, TIM_CHANNEL_4)==HAL_OK)
      {    
        HAL_TIM_OC_Stop_IT(&htim4,TIM_CHANNEL_4) ;   
        flagStop[3] = 1;            
      }
    }
  }
   
  if((flagStop[0]== 1)&&(flagStop[1] == 1)&&(flagStop[2] == 1)&&(flagStop[3] == 1))
  {
    flagStop[0]= 0;
    flagStop[1]= 0;
    flagStop[2]= 0;
    flagStop[3]= 0;

    __HAL_TIM_SET_COUNTER(&htim4,0);
  }
}

4、小結

我們設計了一個四路輸出的脈衝輸出,每一路的輸出數量可以精確單獨控制,在輸出的頻率相對較低而且數量不大的情況下我們驗證是沒有問題的。當然在數量特別多時,是否有偏差我們沒有測試。而在我們使用的平臺,時鐘爲168MHz,根據我們的簡單測試在輸出8MHz的脈衝時還是比較精確的,不過這已經完全滿足一般的應用需求。

其實從STM32的手冊我可以知道,輸出指定脈衝數的方法有多種,但使用主從計時器方式是比較好的一種。這種方式雖然多用了一個定時器,但因爲不需要頻繁中斷大大減少了CPU的處理資源。

歡迎關注:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章