脈沖信號用於設備控制是非常常見的,但在一些情況下,我們希望精確的控制脈沖的數量以實現對運動的精確控制。實現的方式也許有多種多樣,但使用計時器來實現此類操作是人們比較容易想到的。
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、主計時器的配置
首先我們來看一看主計時器的配置,具體代碼如下:
1 /*TIM1初始化配置*/ 2 static void TIM1_Init_Configuration(uint32_t period) 3 { 4 TIM_MasterConfigTypeDef sMasterConfig = {0}; 5 TIM_OC_InitTypeDef sConfigOC = {0}; 6 TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0}; 7 8 htim1.Instance = TIM1; 9 htim1.Init.Prescaler = 1; 10 htim1.Init.CounterMode = TIM_COUNTERMODE_UP; 11 htim1.Init.Period = (period-1); 12 htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; 13 htim1.Init.RepetitionCounter = 0; 14 if (HAL_TIM_PWM_Init(&htim1) != HAL_OK) 15 { 16 Error_Handler(); 17 } 18 sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; 19 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE; 20 if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK) 21 { 22 Error_Handler(); 23 } 24 sConfigOC.OCMode = TIM_OCMODE_PWM1; 25 sConfigOC.Pulse = (period/2); 26 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; 27 sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH; 28 sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; 29 sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET; 30 sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET; 31 if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) 32 { 33 Error_Handler(); 34 } 35 if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2) != HAL_OK) 36 { 37 Error_Handler(); 38 } 39 if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3) != HAL_OK) 40 { 41 Error_Handler(); 42 } 43 if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_4) != HAL_OK) 44 { 45 Error_Handler(); 46 } 47 sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE; 48 sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE; 49 sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF; 50 sBreakDeadTimeConfig.DeadTime = 0; 51 sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE; 52 sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH; 53 sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE; 54 if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK) 55 { 56 Error_Handler(); 57 } 58 59 HAL_TIM_MspPostInit(&htim1); 60 61 }
3.2、從計時器的配置
接着我們再來看一看從計時器的配置,具體代碼如下:
1 /*TIM4初始化配置*/ 2 static void TIM4_Init_Configuration(void) 3 { 4 TIM_ClockConfigTypeDef sClockSourceConfig = {0}; 5 TIM_SlaveConfigTypeDef sSlaveConfig = {0}; 6 TIM_MasterConfigTypeDef sMasterConfig = {0}; 7 8 htim4.Instance = TIM4; 9 htim4.Init.Prescaler = 0; 10 htim4.Init.CounterMode = TIM_COUNTERMODE_UP; 11 htim4.Init.Period = 0xFFFF; 12 htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; 13 if (HAL_TIM_Base_Init(&htim4) != HAL_OK) 14 { 15 Error_Handler(); 16 } 17 sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; 18 if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK) 19 { 20 Error_Handler(); 21 } 22 sSlaveConfig.SlaveMode = TIM_SLAVEMODE_GATED; 23 sSlaveConfig.InputTrigger = TIM_TS_ITR0; 24 if (HAL_TIM_SlaveConfigSynchronization(&htim4, &sSlaveConfig) != HAL_OK) 25 { 26 Error_Handler(); 27 } 28 sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; 29 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; 30 if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK) 31 { 32 Error_Handler(); 33 } 34 35 }
3.3、主輪詢函數實現
主輪詢函數控制着主從計時器的啟動,是實現脈沖輸出的控制者,包括設置脈沖數並開啟從計數器的計數和中斷以及啟動主計時器的輸出。具體代碼如下:
1 /*實現通訊數據的處理*/ 2 void HgraDataProcess(void) 3 { 4 TIM_SET_CAPTUREPOLARITY(&htim1,TIM_CHANNEL_1,TIM_ICPOLARITY_RISING); // 捕獲比較1中斷使能 5 TIM_SET_CAPTUREPOLARITY(&htim1,TIM_CHANNEL_2,TIM_ICPOLARITY_RISING); // 捕獲比較2中斷使能 6 TIM_SET_CAPTUREPOLARITY(&htim1,TIM_CHANNEL_3,TIM_ICPOLARITY_RISING); // 捕獲比較3中斷使能 7 TIM_SET_CAPTUREPOLARITY(&htim1,TIM_CHANNEL_4,TIM_ICPOLARITY_RISING); // 捕獲比較4中斷使能 8 9 __HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_1,6400); // 輸入通道1的捕獲比較值CCR1 10 __HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_2,6400); // 輸入通道2的捕獲比較值CCR2 11 __HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_3,6400); // 輸入通道3的捕獲比較值CCR3 12 __HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_4,6400); // 輸入通道4的捕獲比較值CCR4 13 14 HAL_TIM_OC_Start_IT(&htim4,TIM_CHANNEL_1); //開啟定時器4通道1的輸入捕獲中斷 15 HAL_TIM_OC_Start_IT(&htim4,TIM_CHANNEL_2); //開啟定時器4通道2的輸入捕獲中斷 16 HAL_TIM_OC_Start_IT(&htim4,TIM_CHANNEL_3); //開啟定時器4通道3的輸入捕獲中斷 17 HAL_TIM_OC_Start_IT(&htim4,TIM_CHANNEL_4); //開啟定時器4通道4的輸入捕獲中斷 18 19 HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_1); //開啟定時器1通道1的PWM輸出中斷 20 HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_2); //開啟定時器1通道2的PWM輸出中斷 21 HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_3); //開啟定時器1通道3的PWM輸出中斷 22 HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_4); //開啟定時器1通道4的PWM輸出中斷 23 }
3.4、中斷處理函數的實現
從計時器產生中斷后,會根據不同的中斷調用不同的中斷處理函數,這些回調函數是需要我們實現的,在這里要實現主計時器PWM輸出的停止以及中斷標志的復位等處理。具體實現代碼如下:
1 /*PWM中斷輪詢回調函數*/ 2 static void TIM1_PWM_PulseFinished(TIM_HandleTypeDef *htim) 3 { 4 if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC1) != RESET) //判斷是否生成中斷標志位SR 5 { 6 if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC1) !=RESET) //定時器中斷使能是否開啟 7 { 8 __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_CC1); //清除中斷標志位SR 9 if(HAL_TIM_PWM_Stop_IT(&htim1, TIM_CHANNEL_1)==HAL_OK) //關閉定時器1的通道1的PWM輸出 10 { 11 HAL_TIM_OC_Stop_IT(&htim4,TIM_CHANNEL_1) ; //關閉定時器4的通道1的輸入中斷捕獲 12 flagStop[0] = 1; //關閉標志置1 13 } 14 } 15 } //下面的通道2同理如此 16 if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC2) != RESET) 17 { 18 if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC2) !=RESET) 19 { 20 __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_CC2); //清除標志位 21 22 if(HAL_TIM_PWM_Stop_IT(&htim1, TIM_CHANNEL_2)==HAL_OK) 23 { 24 HAL_TIM_OC_Stop_IT(&htim4,TIM_CHANNEL_2) ; 25 flagStop[1] = 1; 26 } 27 } 28 } 29 if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC3) != RESET) 30 { 31 if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC3) !=RESET) 32 { 33 __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_CC3); //清除標志位 34 35 if(HAL_TIM_PWM_Stop_IT(&htim1, TIM_CHANNEL_3)==HAL_OK) 36 { 37 HAL_TIM_OC_Stop_IT(&htim4,TIM_CHANNEL_3) ; 38 flagStop[2] = 1; 39 } 40 } 41 } 42 if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC4) != RESET) 43 { 44 if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC4) !=RESET) 45 { 46 __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_CC4); //清除標志位 47 48 if(HAL_TIM_PWM_Stop_IT(&htim1, TIM_CHANNEL_4)==HAL_OK) 49 { 50 HAL_TIM_OC_Stop_IT(&htim4,TIM_CHANNEL_4) ; 51 flagStop[3] = 1; 52 } 53 } 54 } 55 56 if((flagStop[0]== 1)&&(flagStop[1] == 1)&&(flagStop[2] == 1)&&(flagStop[3] == 1)) 57 { 58 flagStop[0]= 0; 59 flagStop[1]= 0; 60 flagStop[2]= 0; 61 flagStop[3]= 0; 62 63 __HAL_TIM_SET_COUNTER(&htim4,0); 64 } 65 }
4、小結
我們設計了一個四路輸出的脈沖輸出,每一路的輸出數量可以精確單獨控制,在輸出的頻率相對較低而且數量不大的情況下我們驗證是沒有問題的。當然在數量特別多時,是否有偏差我們沒有測試。而在我們使用的平台,時鍾為168MHz,根據我們的簡單測試在輸出8MHz的脈沖時還是比較精確的,不過這已經完全滿足一般的應用需求。
其實從STM32的手冊我可以知道,輸出指定脈沖數的方法有多種,但使用主從計時器方式是比較好的一種。這種方式雖然多用了一個定時器,但因為不需要頻繁中斷大大減少了CPU的處理資源。
歡迎關注: