定時器相關hal庫函數
hal庫的定時器函數相比于標準庫,多了很多的中斷回調函數,同時對于定時器的初始化也改成使用句柄一次性順帶連帶DMA等功能一起初始化了
typedef struct
{uint32_t Prescaler; /*定時器的預分頻值*/uint32_t CounterMode; /*計數方向,向上計數,向下計數或雙邊計數*/uint32_t Period; /*自動重裝載值*/uint32_t ClockDivision; /*定時器時鐘分頻*/uint32_t RepetitionCounter; /*高級定時器特有的重裝載次數*/uint32_t AutoReloadPreload; /*自動重裝值的更新方式,使能的話,就會使用影子寄存器來等待這次的定時器周期過了才會更新自動重裝值到ARR寄存器,失能的話就是直接更新到ARR*/
} TIM_Base_InitTypeDef;
時基單元的初始化和標準庫的也是幾乎一樣的,多了一個自動重裝值的更新方式。
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
typedef struct __TIM_HandleTypeDef
#else
typedef struct
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
{TIM_TypeDef *Instance; /*TIMX的地址,選擇我們要的定時器*/TIM_Base_InitTypeDef Init; /*定時器初始化結構體*/HAL_TIM_ActiveChannel Channel; /*定時器通道使能*/DMA_HandleTypeDef *hdma[7]; /*DMA結構體初始化*/HAL_LockTypeDef Lock; /*定時器配置鎖定*/__IO HAL_TIM_StateTypeDef State; /*定時器狀態標志*/__IO HAL_TIM_ChannelStateTypeDef ChannelState[4]; /*定時器四個通道狀態*/__IO HAL_TIM_ChannelStateTypeDef ChannelNState[4]; /*高級定時器的四個互補通道狀態*/__IO HAL_TIM_DMABurstStateTypeDef DMABurstState; /*定時器DMA突發傳輸狀態*/
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)void (* Base_MspInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Base Msp Init Callback */void (* Base_MspDeInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Base Msp DeInit Callback */void (* IC_MspInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM IC Msp Init Callback */void (* IC_MspDeInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM IC Msp DeInit Callback */void (* OC_MspInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM OC Msp Init Callback */void (* OC_MspDeInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM OC Msp DeInit Callback */void (* PWM_MspInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM PWM Msp Init Callback */void (* PWM_MspDeInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM PWM Msp DeInit Callback */void (* OnePulse_MspInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM One Pulse Msp Init Callback */void (* OnePulse_MspDeInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM One Pulse Msp DeInit Callback */void (* Encoder_MspInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Encoder Msp Init Callback */void (* Encoder_MspDeInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Encoder Msp DeInit Callback */void (* HallSensor_MspInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Hall Sensor Msp Init Callback */void (* HallSensor_MspDeInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Hall Sensor Msp DeInit Callback */void (* PeriodElapsedCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Period Elapsed Callback */void (* PeriodElapsedHalfCpltCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Period Elapsed half complete Callback */void (* TriggerCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Trigger Callback */void (* TriggerHalfCpltCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Trigger half complete Callback */void (* IC_CaptureCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Input Capture Callback */void (* IC_CaptureHalfCpltCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Input Capture half complete Callback */void (* OC_DelayElapsedCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Output Compare Delay Elapsed Callback */void (* PWM_PulseFinishedCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM PWM Pulse Finished Callback */void (* PWM_PulseFinishedHalfCpltCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM PWM Pulse Finished half complete Callback */void (* ErrorCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Error Callback */void (* CommutationCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Commutation Callback */void (* CommutationHalfCpltCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Commutation half complete Callback */void (* BreakCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Break Callback */
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
} TIM_HandleTypeDef;
?上面是關于定時器的相關配置和能與定時器聯系起來的DMA的相關配置以及所有的中斷回調函數的注冊。
我們使用CubeMX初始化就行了,在Cubemx生成的代碼里面,我們還能看見定時器時鐘選擇,主從模式的失能等操作。
所有的句柄成員都會在對應的.c文件下有著對應的定義,還有Is_xxx這類型的宏定義,來輔助系統對外設初始化的檢測,所有的句柄初始化成功,就返回HAL_OK,錯誤就返回HAL_ERROR并提供給我們一個錯誤回調函數,我們就能在這里面知道對應的外設初始化失敗,需要我們去調試程序。
定時器的中斷使能和中斷優先級設置cubemx都放在了mspInit初始化回調函數里面,但是我們還需要在主函數前面開啟對應的TIMX中斷,才能正常使用。
定時器中斷
設置定時時間,也就是計數值,然后計數值CNT到達ARR后,清空CNT并且觸發一次定時器溢出中斷,通過設置的預分頻值和自動重裝值,控制我們需要的時間。
中斷服務函數都在it.c文件里面找,void TIM2_UP_IRQHandler(void);TIM2更新中斷觸發的時候,就會進入這個中斷服務函數,在里面調用總的定時器中斷服務函數HAL_TIM_IRQHandler。在里面先對定時器TIMX進行判別,再清除對應的中斷請求位,然后進入對應的中斷回調函數。
在對中斷進行編寫的時候,容易遇到芯片的復位鍵無法使用了,這個時候基本就是遇到
中斷處理異常:中斷服務函數編寫錯誤,如未正確清除中斷標志位,導致中斷一直觸發,干擾正常復位流程。這個問題了,我們需要對我們的程序進行檢查和修改。
還有對于我們的用戶自定義函數也得進行檢查,比如說我們移植的OLED函數,就算我們沒有使用OELD_Init,但是我們依舊可以使用OLED里面的顯示函數,還不會報錯,因為沒有初始化,導致OLED的I2C協議沒有正常運行,使得程序卡死在了我們OLED顯示函數那邊,導致程序運行有誤。
我們使用CubeMx來初始化定時器TIM2,ClockSource選擇 Internal Clock
設置預分頻值為7200-1和自動重裝值10000-1,這樣我們得到的計數時間就是
72000000/7200/10000 = 1s;
一秒產生溢出溢出更新中斷。
在NVIC界面勾選TIM2的中斷允許
其他的配置和前面的一樣就行了。
記得開啟中斷
這里我們得注意了,定時器的中斷,需要我們在主函數前面通過
HAL_TIM_Base_Start_IT(&htim2);
來開啟對應的TIMX中斷,沒開啟時中斷是沒響應的。
我們用到的是定時器TIM2的溢出中斷,在HAL_TIM_IRQHandler里面找
里面有各種中斷的標志位判別,以及他們的中斷回調函數。
在里面我們找到TIM_FALG_UPDATE對應的中斷回調函數,就是HAL_TIM_PeriodElapsedCallback()
中斷回調函數都是weak修飾的,弱定義函數,我們直接對其進行內容修改即可。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if(htim == &htim2){Num++; // 每秒遞增1}
}
我們是TIM2的更新中斷,所以在這個函數里面我們得對定時器先進行判別,再對數據進行操作
這樣就能成功實現定時器的溢出中斷計時功能了,OLEDInit別忘記了,也會導致芯片的下載出錯的,但不會鎖死芯片。
定時器輸出比較?
TIM的OC功能,我們常見的就是PWM的輸出,
通過計數值CNT和CCR的比較,控制GPIO口的電平在一定時間內高電平的占比,這就是PWM輸出的思路,這里實現PWM的呼吸燈功能
使用Cubemx來初始化TIM2作為PWM的輸出定時器,使用通道1的PWM模式(pwm模式就是oc的一種功能,只不過運用的比較廣泛,單獨作為一個功能拎出來了),預分頻值和自動重裝載值設置為719和99,自動重裝載值就是一個PWM的周期。
下面的Pulse就是我們要控制的CCR了,控制CCR就能控制PWM的占空比,如果我們一直循環控制CCR從0~100,再從100~0,就能實現我們的呼吸燈功能了。
別忘記? HAL_TIM_PWM_Start_IT(&htim2,TIM_CHANNEL_1);//開啟定時器TIM2的通道1中斷
不開啟的話,是使用不了的,這個函數的功能就是啟動定時器的 PWM 輸出并啟用中斷。
也可以使用 HAL_TIM_PWM_Start,不開啟中斷,開啟PWM輸出,這兩個的區別就是是否啟用中斷,但必須有一個使用,來開啟PWM輸出。
HAL_TIM_PWM_Start_IT(&htim2,TIM_CHANNEL_1);//開啟定時器TIM2的通道1中斷while (1){for(i=0;i<100;i++){__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_1,i);//設置CCR值HAL_Delay(10);}for(i=100;i>0;i--){__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_1,i);//設置CCR值HAL_Delay(10);}}
和江科大一樣的芯片連線就行,使用的是TIM2的通道1,按照硬件電路上看,就是PA0作為PWM的輸出端口。
PWM驅動舵機和直流電機就按照上面的代碼照壺畫瓢就行了。
定時器輸入捕獲
這里需要我們上面的PWM生成的代碼,IC輸入捕獲來測量PWM的頻率,
IC輸入捕獲通過捕獲輸入TIM通道的信號上升沿下降沿,雙邊沿,來觸發中斷,在中斷里面把CNT的值獲取得到對應的時間,這個時間就是我們輸入信號的周期,頻率就是周期的倒數,得到頻率。
CNT是按照時基單元的設置一直自增的。
按照江科大的設置來,定時器2通道1作為PWM的輸出PA0
定時器3通道1 PA6作為輸入引腳。
預分頻值和自動重裝值是決定頻率的值,我們會在主函數里面進行修改,來控制頻率可調,這邊給一個72,得到的頻率就是100000Hz的頻率,自動重裝載值我們給最大值,這樣我們能測量的時間就更大,如果CNT溢出了還沒有接收到信號,我們就定義一個Count值在溢出中斷的時候自增,得到更大的計數值。
捕獲極性這些就用默認設置的即可。
最后設置TIM3的從觸發模式,TI1FP1:表示將 TI1 引腳的信號連接到通道 1的輸入捕獲模塊,作為捕獲源,在這里就是把TIM2的通道1的引腳信號作為TIM3的通道1的輸入捕獲。
TIm2通道1信號到達上升沿后,TIM3收到信號,會自動存儲CNT的值到CCR里面,并重置CNT計數值,我們讀取CCR保存的部分即可。
這邊調用上面的PWM部分的時候不要弄錯預分頻值和自動重裝值,PWM的為719和99
控制為1000HZ的頻率,我們可以在主函數前面通過hal庫的宏定義來修改這兩個參數來獲得不同的PWM頻率,在TIM3處IC捕獲,然后顯示到我們的OLED上。
但是注意,在設置自動重裝值值的時候,自動重裝值不能小于我們的占空比CCR的值,或者說我們修改自動重裝值ARR的時候,順帶修改CCR的值,不然會造成錯誤。
/* USER CODE BEGIN 2 */HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);//開啟TIM2的通道1PWM輸出功能HAL_TIM_IC_Start(&htim3,TIM_CHANNEL_1);//開啟TIM3的通道1IC輸入功能/* USER CODE END 2 */OLED_ShowString(1, 1, "Freq:00000Hz"); //1行1列顯示字符串Freq:00000Hz/* Infinite loop */__HAL_TIM_SET_PRESCALER(&htim2,359);//修改TIM2頻率__HAL_TIM_SET_AUTORELOAD(&htim2,99);//設置自動重裝值__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,50);//修改TIM2通道1的PWM占空比,TIM2的CCR值/* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE */
// OLED_ShowString(2,1,"ADDR");
// OLED_ShowString(3,1,"CCR");
// OLED_ShowNum(2,6,__HAL_TIM_GET_AUTORELOAD(&htim2),4);
// OLED_ShowNum(3,6,__HAL_TIM_GetCompare(&htim2,TIM_CHANNEL_1),4);OLED_ShowNum(1, 6, IC_GetFreq(), 5); //不斷刷新顯示輸入捕獲測得的頻率/* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}uint32_t IC_GetFreq(void){return 1000000/(__HAL_TIM_GET_COMPARE(&htim3,TIM_CHANNEL_1)+1);
}
TIM注意事項
?TIM的注意點還是挺多的,首先就是使用CubeMx初始化完成TIm后,我們要使用他的相關功能,就得在主函數前面進行對應的定時器使能,使用普通的計數計時功能,就只需要時基使能,需要中斷功能的加入,就得中斷使能,運用輸出功能就得OC使能,或者對應的功能使能如PWM使能,IC輸入就得IC使能,普通的Start和startIT的區別就是普通的STart只開啟功能,而startIT不僅會開啟功能,還會開啟對應的相關中斷。