简介
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宏了