1 前言
前幾期我們介紹了STM32CubeMX中關(guān)于定時(shí)器的各個(gè)配置參數(shù)及其功能,本期我們來(lái)介紹各個(gè)功能的具體代碼實(shí)現(xiàn)。
分別是:定時(shí)器中斷,PWM模式,輸出捕獲模式,比較輸出模式,強(qiáng)制輸出模式。
進(jìn)行時(shí)鐘配置,這里主頻是170MHZ,定時(shí)器時(shí)鐘為170MHZ。
2 定時(shí)器中斷
打開(kāi)CubeMX,配置定時(shí)器分頻系數(shù)為170-1,周期計(jì)數(shù)值為1000,主頻為170MHZ。由此我們可以計(jì)算一次溢出的為:
170M/(170+1)/1000 = 1000即1ms一次。
開(kāi)啟定時(shí)器中斷以確保我們的定時(shí)器中斷回調(diào)函數(shù)能被正常觸發(fā)。
接著選擇好編譯器和文件路徑和文件名( 不要用中文路徑)生成工程。
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim1);
/* USER CODE END 2 */
添加啟動(dòng)定時(shí)器并啟用中斷的函數(shù),參數(shù)為我們開(kāi)啟的定時(shí)器句柄。
//開(kāi)啟定時(shí)器并開(kāi)啟中斷
HAL_TIM_Base_Start_IT(&htim1);
//開(kāi)啟定時(shí)器不開(kāi)啟中斷
//HAL_TIM_Base_Start(&htim1);
當(dāng)然也有不啟用中斷的,后者僅僅開(kāi)啟定時(shí)器計(jì)數(shù)功能,但是會(huì)發(fā)生定時(shí)器溢出產(chǎn)生事件更新,不過(guò)不會(huì)觸發(fā)中斷請(qǐng)求。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static int number = 0;
if(htim->Instance == TIM1) // 判斷是否是 TIM1 觸發(fā)的中斷
{
// 1ms觸發(fā)一次
number++;
if(number==1000)
{
//1ms*1000 = 1s
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_5);
number = 0;//清零
}
}
}
接著我們需要重寫(xiě)定時(shí)器中斷回調(diào)函數(shù)來(lái)實(shí)現(xiàn)我們的功能,由于我們定時(shí)器是1ms觸發(fā)一次,因此我們使用一個(gè)計(jì)數(shù)器來(lái)判斷觸發(fā)的次數(shù)。當(dāng)我們定時(shí)時(shí)間到達(dá)1s時(shí),使我們的LED燈翻轉(zhuǎn)。
我們可以看到,中斷回調(diào)函數(shù)在底層被一個(gè)虛函數(shù)定義,這段注釋告訴用戶,不要修改這個(gè)函數(shù)的默認(rèn)實(shí)現(xiàn)。如果需要自定義的回調(diào)行為,應(yīng)該在用戶的代碼中重新實(shí)現(xiàn)這個(gè)回調(diào)函數(shù)。
當(dāng)我們?cè)诔绦蛑行枰薷亩〞r(shí)器觸發(fā)時(shí)間的時(shí)候,我們可以重新修改定時(shí)器的參數(shù)并進(jìn)行初始化。
htim1.Init.Prescaler = new_prescaler;//新的分頻系數(shù)
htim1.Init.Period = new_arr;//新的周期值
HAL_TIM_Base_Init(&htim1); // 重新初始化定時(shí)器
HAL_TIM_Base_Start_IT(&htim1); // 重新啟動(dòng)定時(shí)器
下面要用。
3 PWM模式
上面我們?cè)O(shè)置了定時(shí)器的單周期參數(shù)為1000,接著我們?cè)O(shè)置PWM的比較值(Output compare preload)為500,那么占空比就是:500/1000 = 50%。
接著生成我們的代碼。
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);
/* USER CODE END 2 */
啟動(dòng)定時(shí)器PWM,參數(shù)分別為定時(shí)器句柄和通道。
這樣子我們通道一對(duì)應(yīng)的IO就可以輸出方波了。
htim1.Init.Prescaler = new_prescaler;//新的分頻系數(shù)
htim1.Init.Period = new_arr;//新的周期值
HAL_TIM_Base_Init(&htim1); // 重新初始化定時(shí)器
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1); // 重新啟動(dòng)PWM
通過(guò)修改定時(shí)器的計(jì)數(shù)頻率來(lái)修改PWM的頻率。
/* USER CODE BEGIN 2 */
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, duty); //修改占空比
/* USER CODE END 2 */
通過(guò)修改通道的比較值CCR來(lái)改變PWM的占空比 = CCR/ARR.
4 輸入捕獲
通道配置為輸入捕獲之后,需要注意捕獲模式是上升沿捕獲、下降沿捕獲還是雙邊捕獲。
這里要開(kāi)啟中斷,我們需要使用中斷回調(diào)函數(shù)。
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); // 啟動(dòng)定時(shí)器并使能中斷
調(diào)用開(kāi)啟輸入捕獲的函數(shù),接著重寫(xiě)輸入捕獲的回調(diào)函數(shù)。
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
static uint32_t last_capture = 0; // 上次捕獲的計(jì)數(shù)值
uint32_t capture_value = 0;
if (htim->Instance == TIM1) // 判斷是否是定時(shí)器1觸發(fā)的中斷
{
capture_value = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // 讀取捕獲
// 計(jì)算捕獲值之間的時(shí)間差(信號(hào)周期或脈沖寬度)
if (capture_value > last_capture)
{
uint32_t pulse_width = capture_value - last_capture; // 計(jì)算脈沖寬度
last_capture = capture_value; // 更新上次捕獲值
}
}
}
當(dāng)外部信號(hào)來(lái)臨的時(shí)候,我們可以讀取通道值來(lái)獲得當(dāng)前的計(jì)數(shù)值。通過(guò)比較兩次的計(jì)數(shù)值可以知道兩個(gè)信號(hào)之間的觸發(fā)事件。
這種方法我們也可以用來(lái)測(cè)量PWM的高低電平時(shí)間。通過(guò)修改觸發(fā)方式,當(dāng)上升沿觸發(fā)的時(shí)候,修改觸發(fā)方式為下降沿,就可以知道高電平的時(shí)間。反過(guò)來(lái)就可以知道低電平的時(shí)間。
// 輸入捕獲中斷回調(diào)函數(shù)
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
capture_value = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // 讀取捕獲值
if (htim->Instance == TIM1) // 判斷是否是定時(shí)器2觸發(fā)的中斷
{
if (capture_value > last_capture) // 如果是上升沿
{
// 處理上升沿捕獲邏輯
printf("上升沿時(shí)間: %lun", capture_value);
// 修改下一次捕獲為下降沿觸發(fā)
TIM_IC_InitTypeDef sConfigIC = {0};
sConfigIC.ICPolarity = TIM_ICPOLARITY_FALLING; // 設(shè)置為下降沿觸發(fā)
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; // 直接從輸入端口獲取信號(hào)
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; // 不進(jìn)行預(yù)分頻
sConfigIC.ICFilter = 0; // 無(wú)輸入濾波器
HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1); // 配置通道1為下降沿觸發(fā)
}
else // 如果是下降沿
{
// 處理下降沿捕獲邏輯
printf("下降沿時(shí)間: %lun", capture_value);
// 修改下一次捕獲為上升沿觸發(fā)
TIM_IC_InitTypeDef sConfigIC = {0};
sConfigIC.ICPolarity = TIM_ICPOLARITY_RISING; // 設(shè)置為上升沿觸發(fā)
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; // 直接從輸入端口獲取信號(hào)
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; // 不進(jìn)行預(yù)分頻
sConfigIC.ICFilter = 0; // 無(wú)輸入濾波器
HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1); // 配置通道1為上升沿觸發(fā)
}
last_capture = capture_value; // 更新上次捕獲值
}
}
5 比較輸出
比較輸出的配置和PWM模式的差不多。
HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_1); // 啟動(dòng)通道1的輸出比較
利用該函數(shù)開(kāi)啟通道的比較輸出的功能,它除了能輸出PWM波之外,還可以在比較值的時(shí)候觸發(fā)中斷回調(diào)函數(shù)。
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM1) // 檢查是否是TIM1的比較輸出中斷
{
}
}
并且,當(dāng)我們把模式修改為電平翻轉(zhuǎn)之后,可以讓通道實(shí)現(xiàn)方波輸出。
6 強(qiáng)制輸出
強(qiáng)制輸出模式勝在可以使用軟件強(qiáng)制修改引腳高低電平。
利用如下函數(shù)實(shí)現(xiàn)強(qiáng)制輸出。
// 配置強(qiáng)制輸出模式為強(qiáng)制低電平
__HAL_TIM_OC_SET_FORCED_ACTION(&htim1, TIM_CHANNEL_1, TIM_OCFORCE_LOW);
// 或者配置強(qiáng)制輸出模式為強(qiáng)制高電平
// __HAL_TIM_OC_SET_FORCED_ACTION(&htim1, TIM_CHANNEL_1, TIM_OCFORCE_HIGH);
通過(guò)在定時(shí)器溢出、比較事件或外部觸發(fā)事件發(fā)生時(shí),強(qiáng)制定時(shí)器輸出高電平或低電平,可以靈活地控制STM32微控制器的輸出行為。