电子说
这一次我们继续讲调试方法。调试是排查程序Bug的有效方法,同时也对嵌入式软件设计的可靠性、稳定性而言至关重要。之前讲的调试方法能够打印出变量值、系统状态,或用互动的方式去调试程序,都不能动态的在系统运行时由程序判断变量、参数是否出错。
而我们今天要讲的断言(assert)函数则能做到在运行时判断参数是否超出预设值、状态是否出错,然后打印出出错数据所在的源文件和行号。
那么,什么是断言函数呢?百度百科给的定义是:“断言(assertion)是一种在程序中的一阶逻辑(如:一个结果为真或假的逻辑判断式),目的为了表示与验证软件开发者预期的结果——当程序执行到断言的位置时,对应的断言应该为真。若断言不为真时,程序会中止执行,并给出错误信息。“
接下来,我们继续采用上一次实时跟踪调试的例子,加入断言函数对运行过程的参数进行判断,看看断言函数如何应用,有什么效果。
我们可以在CubeMX中打开例子工程中的.ioc文件,按下图进行设置。
除此之外,可以直接在CubeIDE的工程属性里定义一个宏USE_FULL_ASSERT,也可以在工程任意头文件中定义这个宏,效果是一样的。其实采用CubeMX配置之后,就是在工程的stm2f7xx_hal_conf.h头文件中定义了这个宏。
当定义了宏USE_FULL_ASSERT之后,assert_failed函数就能参与编译了,这个函数在main.c的最下边。这个函数的代码如下:
void assert_failed(uint8_t *file, uint32_t line)
{
printf("Wrong parameters value: file %s on line %drn", file, (uint16_t)line);
}
断言失败的话则会执行这个函数,利用printf打印一条消息,这里我们用的是CubeIDE的ITM模块向外打印,打印的消息里包含断言失败语句所在的源文件及行数。
要注意的是,参数line本来是无符号长整形,printf函数用%d对应长整形的话会给警告,所以做了一个强制类型转换,变为无符号短整型。我想应该不会有一个源文件超过65535行吧,那是要挨打的。
接下来在main.h里定义一个宏IS_PARA_COUNTER_OK,当然名字可以自己任意取。
#define IS_PARA_COUNTER_OK(para) (para < 5)
这个宏的其实是个表达式,用以对para参数的值进行判断,这里假设para的值小于5是正常的。为了防止出错,表达式用小括号括起来了。
在main函数while循环开始的地方,我们加上一条语句,用来对我们设置的一个用来计数的变量counter进行参数断言。
assert_param(IS_PARA_COUNTER_OK(counter));
其中,assert_param是在stm2f7xx_hal_conf.h中定义的一个宏。
#define assert_param(expr) ((expr) ? (void)0U : assert_failed((uint8_t *) FILE , LINE ))
意思是当expr表达式的值为真的时候,不执行任何操作,为假时,断言失败,执行assert_failed函数,并向该函数传递断言失败语句所在的源文件和行。__FILE__和__LINE__都是C语言定义的宏,分别代表当前源文件和所在行。
我们在main函数中写的断言语句可以完全展开如下:
(((counter < 5)) ? (void)0U : assert_failed((uint8_t *)"D:workspaceSTM32F7example2_ITMCoreSrcmain.c", 101))
是的,这条语句位于main.c的101行。
代码修改好后,连接好开发板,构建工程,进入调试模式并开始运行,我们可以在SWV ITM Data Console窗口看到如下信息。
这里要说明一下,代码里counter值是在打印之后加1的,也就是说在打印出4之后,其值已经变为5,导致参数断言出错,打印出预设消息。另外我们也可以在assert_failed函数里加入一个死循环,断言失败后程序就不会继续往下执行了。
全部0条评论
快来发表一下你的评论吧 !