簡介
stm32f10x_conf.h文件有2個作用:①提供對assert_param運行時參數檢查宏函數的定義。②將開發者用到的標准外設頭文件集中在這個文件里面,而stm32f10x_conf.h又被包含到stm32f10x.h中去了,因此方便開發者在寫自己的庫時,只需一股腦的包含stm32f10x.h就行了。
我本人是強烈不推薦第②功能。一個合格的C開發者應該知道它在寫一個模塊時,需要包含什么頭文件,不需要包含什么頭文件。而第②功能的做法就是,不管你用不用,我都全部包含進去。包含不會用到的頭文件一般不是什么錯誤,但是它會影響代碼的編譯速度,代碼的整潔和可讀性。而他的第①功能又可有可無,因此我很早就打算將這個文件從工程中刪除了。
本文主要介紹,在使用ST提供的標准外設驅動庫V3.5.0開發stm32項目時,如何從工程中刪除這個頭文件,而又不影響正常開發。
關於assert_param
ST提供的標准外設庫V3.5.0在實現時,為了防止用戶傳遞的參數不合法,大量使用了運行時斷言。這個斷言函數名為assert_param。
例如庫中的GPIO_ReadInputData函數:
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx) { /* Check the parameters */ assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); //如果參數GPIOx不是一個合法的GPIO,則運行時會調用assert_failed來處理錯誤 return ((uint16_t)GPIOx->IDR); }
assert_param在stm32f10x_conf.h文件中定義,如下:
#ifdef USE_FULL_ASSERT #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__)) void assert_failed(uint8_t* file, uint32_t line);
#else #define assert_param(expr) ((void)0) #endif /* USE_FULL_ASSERT */
解釋:當我們在stm32f10x_conf.h文件定義了 USE_FULL_ASSERT(代表開發者需要啟用標准庫的運行時參數檢查機制),那么assert_param作用為:即當參數為false時,調用assert_failed函數,否則當參數為true時,等於(void)0,也就是什么都不做。而assert_failed是一個留給開發者自己去實現的函數,他通常實現在main.c中,主體通常是一個死循環,其參數是錯誤發生的文件名和代碼行號。也就是說當你在調用一個標准庫的函數時,如果參數傳遞不合法,那么就會陷入死循環,因為程序沒有繼續允許下去的意義了。如果合法,則什么也不會發生。
當我們在stm32f10x_conf.h文件中不定義 USE_FULL_ASSERT(代表開發者不要啟用標准庫的運行時參數檢查機制),assert_param總是等價於 :((void)0),即總是不起任何作用。這樣就無法檢測調用標准庫時參數是否傳遞錯誤。
//------------文件:main.c------------------ #ifdef USE_FULL_ASSERT void assert_failed(uint8_t* file, uint32_t line) { while (1) { } } #endif
所以,USE_FULL_ASSERT是一個功能裁剪宏,定義后,標准庫會啟用參數檢查機制;取消它的定義, 標准庫會禁用參數檢查機制。
第一個問題
為什么我們要使用V3.5.0標准庫,就必須將stm32f10x_conf.h包含到工程中去(具體說是將stm32f10x_conf.h包含到stm32f10x.h中去)?
答:因為標准庫V3.5.0為了實現運行時參數檢查,使用了assert_param宏函數,而這個宏函數定義在stm32f10x_conf.h文件中。如果不包含這個文件到項目中去,編譯器在編譯標准庫時,就會找不到這個函數的定義而報錯。
第二個問題
我們如何將stm32f10x_conf.h包含到工程中去?
答:
在stm32f10x.h中,有如下代碼片段:
//-----------文件:stm32f10x.h-------------- #if !defined USE_STDPERIPH_DRIVER /** * @brief Comment the line below if you will not use the peripherals drivers. In this case, these drivers will not be included and the application code will be based on direct access to peripherals registers */ /*#define USE_STDPERIPH_DRIVER*/ #endif //==省略== //==省略== //==省略== #ifdef USE_STDPERIPH_DRIVER #include "stm32f10x_conf.h" #endif
也就是說我們需要在stm32f10x.h中定義 USE_STDPERIPH_DRIVER 這個宏,才能將stm32f10x_conf.h包含到stm32f10x.h中去。而定義USE_STDPERIPH_DRIVER這個宏有2種方法:①修改stm32f10x.h,開啟對USE_STDPERIPH_DRIVER的宏定義。②在編譯器的宏定義選項中添加USE_STDPERIPH_DRIVER的定義。
然而為了防止開發者誤修改stm32f10x.h文件,ST將這個文件的屬性設置為只讀的,因此第一種方法是不建議的。
第三個問題
為什么我們使用標准外設庫V3.5.0,就必須提供對USE_STDPERIPH_DRIVER宏的定義?
答:結合上面的問題一和問題二就明白了。
最后一個問題
我們到底要不要啟用標准庫的運行時參數檢查機制?
答:如果啟用,則標准庫的代碼會在運行時執行參數檢查,影響庫函數的執行效率,生成的hex文件的也會膨脹。所以建議在開發時啟用標准庫的運行時參數檢查機制,項目穩定后,發布時可以關閉。
移除stm32f10x_conf.h文件
最開始我已經表達了對stm32f10x_conf.h的態度,我不打算使用這個頭文件,甚至以后得工程都不再使用它。但我又需要使用標准庫,那就必須在stm32f10x.h中直接提供對assert_param的定義,而不是放在stm32f10x_conf.h中。
第一步:【右鍵】stm32f10x.h,將其只讀屬性取消,這樣就可以修改它了。
第二步:
刪除stm32f10x.h中的下面的代碼
#if !defined USE_STDPERIPH_DRIVER /** * @brief Comment the line below if you will not use the peripherals drivers. In this case, these drivers will not be included and the application code will be based on direct access to peripherals registers */ /*#define USE_STDPERIPH_DRIVER*/ #endif
按如下操作繼續修改stm32f10x.h
/************************************************************************************* 如果開發者需要啟用標准庫的運行時參數檢查機制,則在編譯器的宏定義選項中定義STDPERIPH_DRIVER_USE_ASSERT 如果開發者不想啟用標准庫的運行時參數檢查機制,則不定義STDPERIPH_DRIVER_USE_ASSERT 提示:啟用標准庫的運行時參數檢查機制,導致運行時執行參數檢查代碼,影響庫函數的執行效率和生成的hex文件的體積。 建議在開發時啟用標准庫的運行時參數檢查機制,發布時可以不啟用。 */ #ifdef STDPERIPH_DRIVER_USE_ASSERT #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__)) void assert_failed(uint8_t* file, uint32_t line); #else #define assert_param(expr) ((void)0) #endif //====================用上面的代碼替換stm32f10x.h中的下面的代碼=====================
#ifdef USE_STDPERIPH_DRIVER #include "stm32f10x_conf.h" #endif
第三步:
把stm32f10x.h文件的屬性改回為只讀,防止開發時錯誤的修改。
最后對main.c的修改:
如果開啟了標准庫的運行時參數檢查機制,則還要在main.c中提供對assert_failed函數的實現,用於在參數檢查錯誤時處理這個錯誤。
因此替換原先的assert_failed的定義如下:
#ifdef STDPERIPH_DRIVER_USE_ASSERT //當標准外設庫函數進行參數合法性檢查發現不通過時,調用的回調處理函數 //file為錯誤發生的文件名,line為錯誤行號 void assert_failed(uint8_t* file, uint32_t line) { while (1) { } } #endif
如何使用
通過上面的修改,我們已經不再需要stm32f10x_conf.h頭文件(和里面的USE_FULL_ASSERT宏),也不再需要USE_STDPERIPH_DRIVER宏。取而代之的是一個新的宏:STDPERIPH_DRIVER_USE_ASSERT。
建議通過上面的步驟,構建一個自己的開發模板,以便后續繼續使用。
這樣做的好處
1、工程少了一個頭文件,管理起來更加方便
2、在工程中使用標准外設庫時,不再被強迫定義USE_STDPERIPH_DRIVER宏了