使用HAL庫開發STM32:系統時間基礎及進階使用

目的

HAL庫默認提供了系統時間,系統時間默認情況下由SysTick定時器計數產生。系統時間一方面用於HAL庫自身調用,另一方面用戶也可以使用,爲開發帶來便利。(本文提到的相關使用主要應用於未使用OS(操作系統)的情況下。)

基礎使用

一般的系統時間使用方面常用到兩個函數:

  • __weak uint32_t HAL_GetTick(void)
    返回從系統運行開始經過的時間,默認情況下單位爲ms;
  • __weak void HAL_Delay(uint32_t Delay)
    延時,該延時是阻塞的,默認情況下延時單位爲ms,該函數不能在等於或高於系統時鐘源優先級(默認情況下爲0)的中斷程序中使用,不然程序就阻塞在這裏不動了;

對於上面兩個函數本身來說沒什麼特別可以多說的,需要注意的點也在上面說明了。上面的延時函數是阻塞型的,當然我們也有方式實現非阻塞的延時。

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();

    while (1)
    {
        HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0);
        HAL_Delay(1000);
    }
}

上面方式使用HAL_Delay()進行延時,可以實現GPIOA0口每秒反轉一次電平。如果只有一個任務這樣就沒問題,但是如果有多個對延時時間有不同需求的任務這樣就不太合適了,這時候可以使用下面方式:

uint32_t previousMillisA0 = 0;
uint32_t previousMillisA1 = 0;

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();

    while (1)
    {
        uint32_t currentMillis = HAL_GetTick(); //獲取當前系統時間
        if (currentMillis - previousMillisA0 >= 1000) //當前時間刻減去前次執行的時間刻
        {
            previousMillisA0 = currentMillis; //更新執行時間刻
            HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0);
        }
        if (currentMillis - previousMillisA1 >= 500) //當前時間刻減去前次執行的時間刻
        {
            previousMillisA1 = currentMillis; //更新執行時間刻
            HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1);
        }
    }
}

上面的代碼實現了GPIOA0口每秒反轉一次電平,同時GPIOA1口每500毫秒反轉一次電平。可以使用這種方式處理更多的任務。

進階使用

HAL的系統時間由定時器在中斷中累加計數:

/**
  * @brief This function is called to increment  a global variable "uwTick"
  *        used as application time base.
  * @note In the default implementation, this variable is incremented each 1ms
  *       in SysTick ISR.
 * @note This function is declared as __weak to be overwritten in case of other 
  *      implementations in user file.
  * @retval None
  */
__weak void HAL_IncTick(void)
{
  uwTick += uwTickFreq;
}

因爲這個回調函數是由 __weak 符號修飾的,所以可以自己重新寫同名函數來實現更復雜的功能,比如可以用來實現調度器功能。先看下面演示:
在這裏插入圖片描述
上圖例子中聲明瞭兩個任務,分別設置運行參數並運行。通過圖中可以看到兩個任務都按期望的方式執行了。

以下是Ticker部分代碼:

#ifndef LIB_TICKER_H_
#define LIB_TICKER_H_

#include "main.h"

#define LIB_TICKER_MAX_SIZE 16 // 最大Ticker可綁定數

class LibTicker {
public:
	LibTicker(void);
	~LibTicker(void);

	typedef void (*callback_t)(void);
	typedef void (*callback_with_arg_t)(void*);

	// 設置Ticker定期執行,輸入參數分別是時間(毫秒)、回調函數
	bool attach(size_t milliseconds, callback_t callback) {
		if (!milliseconds) {
			return false;
		}
		return _attach(milliseconds, milliseconds, reinterpret_cast<callback_with_arg_t>(callback), 0);
	}

	// 設置Ticker定期執行,輸入參數分別是時間(毫秒)、回調函數、不大於32位變量
	template<typename T>
	bool attach(size_t milliseconds, void (*callback)(T), T arg) {
		if (!milliseconds) {
			return false;
		}
		if (sizeof(T) > sizeof(size_t)) {
			return false;
		}
		uint32_t arg32 = (uint32_t) arg;
		return _attach(milliseconds, milliseconds, reinterpret_cast<callback_with_arg_t>(callback), arg32);
	}

	// 設置Ticker執行一次,輸入參數分別是時間(毫秒)、回調函數
	bool once(size_t milliseconds, callback_t callback) {
		return _attach(0, milliseconds, reinterpret_cast<callback_with_arg_t>(callback), 0);
	}

	// 設置Ticker執行一次,輸入參數分別是時間(毫秒)、回調函數、不大於32位變量
	template<typename T>
	bool once(size_t milliseconds, void (*callback)(T), T arg) {
		if (sizeof(T) > sizeof(size_t)) {
			return false;
		}
		uint32_t arg32 = (uint32_t) arg;
		return _attach(0, milliseconds, reinterpret_cast<callback_with_arg_t>(callback), arg32);
	}
	void detach(void); // 停止已綁定運行的Ticker
	bool active(void); // 返回Ticker當前是否已綁定
	static void handle(void); // 執行已就緒的任務,該函數可以設置成main(){while(1){LibTicker::handle();}}
	static void schedule(void);

private:
	bool _active;
	size_t _index;
	bool _ready;
	size_t _period;
	size_t _count;
	callback_with_arg_t _callback;
	size_t _arg;
	bool _attach(size_t period, size_t count, callback_with_arg_t callback, size_t arg);
	static LibTicker *_ticker[LIB_TICKER_MAX_SIZE];
};

#endif /* LIB_TICKER_H_ */
#include "lib_ticker.h"

typedef void (*LibTickerCallBack)(size_t arg);

LibTicker::LibTicker(void) :
		_active(false), _ready(false), _period(0), _count(0), _callback(nullptr), _arg(0) {
}

LibTicker::~LibTicker(void) {
	detach();
}

void LibTicker::detach(void) {
	if (_active) { // 如果Ticker當前已綁定運行
		__disable_irq();
		_active = false;
		_ready = false;
		_period = 0;
		_callback = nullptr;
		_arg = 0;
		_ticker[_index] = nullptr;
		__enable_irq();
	}
}

bool LibTicker::active(void) {
	return _active;
}

bool LibTicker::_attach(size_t period, size_t count, callback_with_arg_t callback, size_t arg) {
	if (callback == nullptr) {
		return false;
	}
	if (_active) { // 如果Ticker當前已綁定運行
		_ready = false;
		_period = period;
		_count = count;
		_callback = callback;
		_arg = arg;
		return true;
	} else {
		for (size_t i = 0; i < LIB_TICKER_MAX_SIZE; i++) {
			if (_ticker[i] == nullptr) {
				__disable_irq();
				_active = true;
				_index = i;
				_ready = false;
				_period = period;
				_count = count;
				_callback = callback;
				_arg = arg;
				_ticker[i] = this;
				if (_count == 0) {
					_ready = true;
				}
				__enable_irq();
				return true;
			}
		}
	}
	return false;
}

void LibTicker::handle(void) {
	for (size_t i = 0; i < LIB_TICKER_MAX_SIZE; i++) {
		if (_ticker[i] == nullptr) {
			continue;
		}
		if (!_ticker[i]->_active) {
			continue;
		}
		if (_ticker[i]->_ready) { // 當前Ticker已就緒
			_ticker[i]->_ready = false;
			_ticker[i]->_callback(reinterpret_cast<void*>(_ticker[i]->_arg));
			if ((_ticker[i]->_period == 0) && (_ticker[i]->_count == 0)) { // 該任務只運行一次
				_ticker[i]->detach();
			}
		}
	}
}

void LibTicker::schedule(void) {
	for (size_t i = 0; i < LIB_TICKER_MAX_SIZE; i++) {
		if (_ticker[i] == nullptr) {
			continue;
		}
		if (!_ticker[i]->_active) {
			continue;
		}
		if (_ticker[i]->_count) {
			_ticker[i]->_count--;
		}
		if (_ticker[i]->_count == 0) {
			_ticker[i]->_ready = true;
			_ticker[i]->_count = _ticker[i]->_period;
		}
	}
}

LibTicker * LibTicker::_ticker[LIB_TICKER_MAX_SIZE] = { nullptr };

extern __IO uint32_t uwTick;
extern HAL_TickFreqTypeDef uwTickFreq;
void HAL_IncTick(void) { // 重寫系統時間計數函數
	uwTick += uwTickFreq; // 保留系統時間計數功能
	LibTicker::schedule(); // 進行Ticker調度處理
}

上面的例子打包下載:
《基於STM32 HAL庫的定時任務調度器例程 stm32f405ticker.zip》
https://download.csdn.net/download/naisu_kun/11913140
另外也可以參考GitHub上項目,命名稍有調整,功能並沒有改動:
https://github.com/NaisuXu/STM32-tool-library-based-on-HAL-and-LL

總結

系統時間在開發過程中還是比較有用的,上面只是列舉了部分常見用法。如果對時間有更精密的需求的話推薦使用定時器。

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