1.1 背景
目前市面上的單片機基本都帶有硬件定時器功能,單片機應用程序開發中也經常會用到定時器進行一些和時間相關的開發,比如延時或者周期性地執行一些操作。單片機的硬件定時器個數一般都是固定的,而且一些低端單片機的定時器個數一般都比較少,在一些有多個周期性操作的應用場合就無法滿足要求。這時,就可以基于硬件定時器派生出軟件定時器,來滿足這種多種周期性或多個單次延時操作的需求。軟件定時器的優點就是個數可以根據實際需求進行靈活配置,而且可以實現多種不同的定時周期。
1.2 測試平臺
這里使用的開發環境和相關硬件如下。
- 操作系統:Ubuntu 20.04.2 LTS x86_64(使用uname -a命令查看)
- 集成開發環境(IDE):Eclipse IDE for Embedded C/C++ Developers,Version: 2021-06 (4.20.0)
- 硬件開發板:STM32F429I-DISCO
- 本文對應的例程代碼鏈接如下。
https://download.csdn.net/download/goodrenze/85106391
1.3 軟件定時器實現方法
這里就結合開發板STM32F429I-DISCO上的STM32F429ZI的單片機來演示軟件定時器的實現方法。
一般定時器的計數方式有2種:一種是單次定時,即定時時間到了之后,自動停止定時;另一種是周期定時,定時時間到了之后,自動按照之前的定時周期重新定時。對于周期定時,可以手動進行定時器的啟動、關閉和刪除。
下面講解軟件定時器的實現步驟。
1)由于軟件定時器是基于硬件定時器的,所以需要先初始化一個硬件定時器,并啟動硬件定時器。這里使用STM32F429ZI的硬件定時器7,定時器的定時周期為10ms,即每10ms產生一次定時器中斷。初始化代碼如下。
TIM_HandleTypeDef Tim7Handle;
uint8_t InitTim7(uint32_t period_ms)
{
uint16_t uwPrescalerValue;
if(0 == period_ms)
{
return 1;
}
__HAL_RCC_TIM7_CLK_ENABLE();
HAL_NVIC_SetPriority(TIM7_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(TIM7_IRQn);
/* Compute the prescaler value to have TIM7 counter clock equal to 10 KHz */
uwPrescalerValue = (uint32_t) ((SystemCoreClock /2) / 10000) - 1;
/* Set TIM7 instance */
Tim7Handle.Instance = TIM7;
Tim7Handle.Init.Period = period_ms * 10 - 1;
Tim7Handle.Init.Prescaler = uwPrescalerValue;
Tim7Handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
Tim7Handle.Init.CounterMode = TIM_COUNTERMODE_UP;
if(HAL_TIM_Base_Init(&Tim7Handle) != HAL_OK)
{
return 1;
}
/* Start the TIM Base generation in interrupt mode */
if(HAL_TIM_Base_Start_IT(&Tim7Handle) != HAL_OK)
{
return 1;
}
return 0;
}
2)硬件定時器定時時間到了之后,會產生中斷,所以需要實現定時器中斷處理函數。這里基于STM32的HAL進行開發,所以在定時器的中斷入口函數中直接調用HAL_TIM_IRQHandler()函數,然后實現實際的中斷處理回調函數HAL_TIM_PeriodElapsedCallback()。對應的代碼如下。其中調用的軟件定時器更新函數會在后面介紹。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim == &Tim7Handle)
{
SwTimerUpdateCount();
}
}
3)設計軟件定時器對應的結構體。按照軟件定時器的實際使用特點,必須要包含定時器計數值和周期定時器的重裝載值,另外還要有定時器時間到之后需要執行的回調函數。對應的軟件定時器結構體如下所示。當結構體中的TimerCount和TimerReload都為0時,說明軟件定時器處于空閑狀態,可以分配使用;如果TimerCount非0,而TimerReload為0,說明軟件定時器是單次定時器;如果TimerCount和TimerReload都非0,說明軟件定時器是周期定時器。
typedef void (*TimerCallbackFunc)(void);
typedef struct _SwTimer_t
{
uint32_t TimerCount;
uint32_t TimerReload;
TimerCallbackFunc TimerCallback;
}SwTimer_t;
4)這里將軟件定時器設置成10個,可以通過宏定義來設置軟件定時器個數。使用軟件定時器結構體定義一個具有10個定時器的數組。如下代碼所示。
#define SW_TIMER_NUM 10
SwTimer_t SwTimer[SW_TIMER_NUM];
5)軟件定時器復位函數,用于實現所有軟件定時器的重置操作,重置后,所有軟件定時器都處于空閑狀態,可供分配使用。函數代碼如下。
void SwTimerReset(void)
{
uint8_t i;
for(i = 0; i < SW_TIMER_NUM; i++)
{
SwTimer[i].TimerCount = 0;
SwTimer[i].TimerReload = 0;
SwTimer[i].TimerCallback = 0;
}
}
6)啟動單次定時器函數,用于實現單次定時,定時時間到了之后,執行對應回調函數,并停止定時和釋放定時器資源。函數代碼如下。如果啟動成功,函數返回定時器的索引號(該值小于定時器個數值);啟動失敗,返回的定時器索引號等于定時器的個數。
uint8_t SwTimerStartSingleTimer(uint32_t single_ms, TimerCallbackFunc TimerCallback)
{
uint8_t i;
single_ms /= MINI_PERIOD_MS;
if(0 == single_ms)
{
single_ms = 1;
}
for(i = 0; i < SW_TIMER_NUM; i++)
{
if((SwTimer[i].TimerCount == 0) && (SwTimer[i].TimerReload == 0))
{
SwTimer[i].TimerCount = single_ms;
SwTimer[i].TimerCallback = TimerCallback;
break;
}
}
return i;
}
7)添加周期定時器函數,用于添加一個新的周期定時器但不啟動定時,需要手動啟動定時器。函數代碼如下。如果添加成功,函數返回定時器的索引號(該值小于定時器個數值);添加失敗,返回的定時器索引號等于定時器的個數。
uint8_t SwTimerAddPeriodTimer(uint32_t period_ms, TimerCallbackFunc TimerCallback)
{
uint8_t i;
period_ms /= MINI_PERIOD_MS;
if(0 == period_ms)
{
period_ms = 1;
}
for(i = 0; i < SW_TIMER_NUM; i++)
{
if((SwTimer[i].TimerCount == 0) && (SwTimer[i].TimerReload == 0))
{
SwTimer[i].TimerReload = period_ms;
SwTimer[i].TimerCallback = TimerCallback;
break;
}
}
return i;
}
8)啟動周期定時器函數,用于啟動指定定時器索引號的周期定時器開始定時。函數代碼如下。如果啟動成功,函數返回定時器的索引號(該值小于定時器個數值);啟動失敗,返回的定時器索引號等于定時器的個數。
uint8_t SwTimerStartPeroidTimer(uint8_t timer_no)
{
if(SW_TIMER_NUM <= timer_no)
{
return SW_TIMER_NUM;
}
else if((SwTimer[timer_no].TimerCount == 0) && (SwTimer[timer_no].TimerReload == 0))
{
return SW_TIMER_NUM;
}
else
{
SwTimer[timer_no].TimerCount = SwTimer[timer_no].TimerReload;
return timer_no;
}
}
9)停止定時器函數,用于結束指定定時器索引號的定時器的定時,可用于停止單次或周期定時器。函數代碼如下。如果停止成功,函數返回定時器的索引號(該值小于定時器個數值);停止失敗,返回的定時器索引號等于定時器的個數。
uint8_t SwTimerStopTimer(uint8_t timer_no)
{
if(SW_TIMER_NUM <= timer_no)
{
return SW_TIMER_NUM;
}
else
{
SwTimer[timer_no].TimerCount = 0;
return timer_no;
}
}
10)刪除周期定時器函數,用于結束指定定時器索引號的周期定時器的定時,并釋放定時器資源。函數代碼如下。如果刪除成功,函數返回定時器的索引號(該值小于定時器個數值);刪除失敗,返回的定時器索引號等于定時器的個數。
uint8_t SwTimerDeletePeroidTimer(uint8_t timer_no)
{
if(SW_TIMER_NUM <= timer_no)
{
return SW_TIMER_NUM;
}
else
{
SwTimer[timer_no].TimerCount = 0;
SwTimer[timer_no].TimerReload = 0;
return timer_no;
}
}
11)軟件定時器計數值更新函數,用于更新每個已經啟動定時的軟件定時器的計數值,該函數必須在硬件定時器的中斷處理函數中調用。函數的實現思路是:遍歷所有的軟件定時器,如果遍歷到的定時器的計數值非0,則進行減1操作。如果減1后計數值為0,如果定時器的重裝載值非0,說明是周期定時器,需要將計數值更新成對應的重裝載值以便重新定時,同時執行對應的回調函數;如果定時器的重裝載值是0,說明是單次定時器,執行完回調函數后自動停止定時并釋放定時器資源。如果減1后計數值不為0,繼續遍歷更新后續的定時器,直到所有定時器都遍歷完畢。函數流程圖和對應代碼如下。
圖1 軟件定時器計數值更新函數
void SwTimerUpdateCount(void)
{
uint8_t i;
for(i = 0; i < SW_TIMER_NUM; i++)
{
if(SwTimer[i].TimerCount != 0)
{
SwTimer[i].TimerCount -= 1;
if(SwTimer[i].TimerCount == 0)
{
if(SwTimer[i].TimerReload != 0)
{
SwTimer[i].TimerCount = SwTimer[i].TimerReload;
}
if(SwTimer[i].TimerCallback != 0)
{
SwTimer[i].TimerCallback();
}
}
}
}
}
12)軟件定時器的實際使用示例。代碼如下。
int main(void)
{
uint8_t no;
HAL_Init();
/* Configure the system clock to 168 MHz */
SystemClock_Config();
BSP_LED_Init(LED3);
BSP_LED_Init(LED4);
InitTim7(MINI_PERIOD_MS);
SwTimerReset();
no = SwTimerAddPeriodTimer(500, ToggleLed3);
if(no < SW_TIMER_NUM)
{
SwTimerStartPeroidTimer(no);
}
#if 1
no = SwTimerAddPeriodTimer(1000, ToggleLed4);
if(no < SW_TIMER_NUM)
{
SwTimerStartPeroidTimer(no);
}
#else
no = SwTimerStartSingleTimer(5000, ToggleLed4);
#endif
while (1)
{
}
}
-
單片機
+關注
關注
6037文章
44561瀏覽量
635584 -
STM32
+關注
關注
2270文章
10901瀏覽量
356195 -
軟件定時器
+關注
關注
0文章
18瀏覽量
6760 -
定時器
+關注
關注
23文章
3250瀏覽量
114865 -
函數
+關注
關注
3文章
4332瀏覽量
62653
發布評論請先 登錄
相關推薦
評論