利用串口调试单片机程序以及宏assert_param的说明

     先来讲一下 a s s e r t _ p a r a m assert\_param assert_param宏,其实这个在 s t m 32 stm32 stm32的模块的固件库函数中很常见,就是用来判断给函数的参数是否符合要求。以前也没仔细看,一次发现我给的参数不符合要求,但是程序好像也没有出现什么问题,这时我就准备好好看一看了。
     在任意一个使用该宏的固件库函数中,右击该宏并在弹出的下拉菜单中选择 G o   T o   D e f i n i t i o n   O f   ′ a s s e r t _ p a r a m ′ Go\ To\ Definition\ Of\ 'assert\_param' Go To Definition Of assert_param即可找到该宏定义的位置,如图1所示。从图1中可以看出,预处理之后该宏的内容被替换为 ( ( v o i d ) 0 ) ((void)0) ((void)0)。现在有郁闷了 ( ( v o i d ) 0 ) ((void)0) ((void)0)这玩意儿是干嘛的?我这里没有去查找大量资料,我觉得这位哥们儿写的挺不错的,他的参考的内容也不错,有兴趣的朋友可以花时间去看看。 ( ( v o i d ) 0 ) ; ((void)0); ((void)0);语句的作用就是空语句什么都不操作。常用于程序的调试阶段中 D D E B U G DDEBUG DDEBUG相关函数中。当不再想使用 D D E B U G DDEBUG DDEBUG的相关功能时,就可以用该空语句代替该 D D E B U G DDEBUG DDEBUG相关的宏。
     从图1中可以看出因为这里我们没有定义 U S E _ F U L L _ A S S E R T USE\_FULL\_ASSERT USE_FULL_ASSERT,所以所有使用该宏的地方其实没有任何作用,全部被空语句替代。所以就算给的参数不符合要求,程序也不会报错。那我们现在定义 U S E _ F U L L _ A S S E R T USE\_FULL\_ASSERT USE_FULL_ASSERT,如图2所示。此时该宏当参数满足要求时和上面一样也是被空语句代替,没有任何作用。但是当参数不满足要求是会调用函数 v o i d   a s s e r t _ f a i l e d ( u i n t 8 _ t ∗   f i l e ,   u i n t 32 _ t   l i n e ) ; void\ assert\_failed(uint8\_t*\ file,\ uint32\_t\ line); void assert_failed(uint8_t file, uint32_t line);且函数参数分别为预定义的宏 _ F I L E _ \_FILE\_ _FILE_ _ L I N E _ \_LINE\_ _LINE_,它们分别代表当前源文件的文件名以及 a s s e r t _ p a r a m assert\_param assert_param宏在该文件中所在的行数。如果我们现在直接编译的话会出现如图3所示的编译错误,提示函数函数 v o i d   a s s e r t _ f a i l e d ( u i n t 8 _ t ∗   f i l e ,   u i n t 32 _ t   l i n e ) ; void\ assert\_failed(uint8\_t*\ file,\ uint32\_t\ line); void assert_failed(uint8_t file, uint32_t line);没有定义,我们自己定义一下该函数就可以了,如下所示,打印出错的地方所在的源文件以及行数。现在如果从参数不正确的话就会打印出错的地方所在的源文件以及行数。

void assert_failed(uint8_t* file, uint32_t line)
{
     printf("Wrong parameters value: file %s on line %d\r\n", file, line);
     return;
}
 
图1.
 
图2.
 
图3.

     我们可以简单的测试一下,如图4所示。

 
图4.

     我们再来复习一点小知识:C语言的sizeof(字符数组名),C语言的strlen(字符数组名),C语言的字符串结束标志’\0’。因为在C语言中没有字符串这一种类型(也即是没有字符串变量),只有字符类型(char),因此C语言会在字符串常量,比如 " i u t i d t s i d g s i g d " "iutidtsidgsigd" "iutidtsidgsigd",的最后自动加上一个字符结束标志’\0’,也即是 A S C I I ASCII ASCII码为0的字符。C语言的求字符串长度的函数 s i z e _ t s t r l e n ( c o n s t c h a r ∗ s t r ) size\_t\quad strlen(const\quad char *\quad str) size_tstrlen(constcharstr)在求字符串长度的时候也是以第一次遇到的字符结束标志’\0’来判断字符串的长度。在用sizeof(字符数组名)来求字符数组的长度的时候得到的是数组实际占用的内存字节数。我们可以看一下图5中的 a r r a y 4 array4 array4 a r r a y 5 array5 array5,这两个数组在定义和初始化的时候没有定义数组的长度, a r r a y 4 array4 array4是用字符串常量的形式进行初始化的,因此系统会在末尾自动加上一个字符结束标志’\0’,导致其调用sizeof函数后得到的值是5。 a r r a y 5 array5 array5是用单个字符的形式进行初始化的且没有加上字符结束标志’\0’,因此在用strlen(字符数组名)求数组长度的时候,函数strlen会一直寻找第一个字符结束标志’\0’,直到找到为止才计算所求长度。因为这里我们并没有加上字符结束标志’\0’,因此这里第一个字符结束标志’\0’的为止是随机的,在不同的系统会运行次数下结果可能会不同,这里求得的是8。 a r r a y 3 array3 array3是用单个字符的形式进行初始化的且在索引为5的位置初始化为字符结束标志’\0’,因此用strlen(字符数组名)求得的值为5。还有就是如果定义和初始化数组的时候,如果指定了数组的长度且指定的数组的长度字大于字符串初始化形式或单个字符初始化形式中的字符的个数(如果是字符串初始化形式会自动加上字符串结束标志’\0),字符数组中空余的空间一般也会被自动初始化为0,也就是字符结束标志’\0’。例如对应下面的第一个数组初始化,第2到第9个数组空间会被自动初始化为0。对于第二个数组初始化,第8和第9个数组空间会被自动初始化为0。数组索引从0开始。

char array[10]={'1','2'};
char array[10]="qwqwqwq";
 
图5.

     下面来介绍一种在单片机环境下利用串口输入的信息来调试程序的方法。我先把一个简单的基于 s t m 32 f 10 stm32f10 stm32f10系列芯片的 k e i l keil keil工程的框图放出来。如图6所示。

 
图6.

      C O R E CORE CORE文件夹和 L I B LIB LIB文件夹就很简单了,就是芯片内核文件和相关的驱动文件。 U S E R USER USER文件夹里面有 m a i n main main函数。这里重点是 U S A R T USART USART文件夹, M E S S A G E MESSAGE MESSAGE文件夹和 T E S T C A S E TESTCASE TESTCASE文件夹。

  • U S A R T USART USART文件夹里面的 u s a r t . c usart.c usart.c文件主要是串口配置和接收字符串的相关函数。上位机发送的字符串一般形式为 { 属 性 : 值 , 属 性 : 值 , 属 性 : 值 , . . . . . } \{属性:值,属性:值,属性:值,.....\} {,,,.....},例如 { " c o m m a n d " : " r e a d " , " c a s e " : 586 } \{"command":"read","case":586\} {"command":"read","case":586},属性可以理解为参数名,值可以理解为参数值。
  • M E S S A G E MESSAGE MESSAGE文件夹里面的 m e s s a g e . c message.c message.c文件主要是从串口接收的字符串中提取对应的参数的值,这样我们就可以通过串口发送不同的参数以及不同的值,进而调用不同的测例进行测试。这里从字符串中提取对应的参数的值用到了串的模式匹配算法(KMP)
  • T E S T C A S E TESTCASE TESTCASE文件夹存放具体的测例,根据从串口接收到的参数的值,调用特定的测例。

     下面我们看一下 T E S T C A S E TESTCASE TESTCASE文件夹里面的测例的内容。不同的测例 c a s e 1 , c a s e 2 和 c a s e 3 case1,case2和case3 case1,case2case3只在从串口获取的 t e s t C a s e I n d e x testCaseIndex testCaseIndex参数和该测例的对应值相等时该测例才会被调用。

/*case1.h*/
#ifndef _CASE1_H
#define _CASE1_H

void case1();
#endif
/*case1.c*/
#include "case1.h"
#include <stdio.h>
#include "test.h"

void case1()
{
    if(testCaseIndex==CASE1_INDEX)
    {
        printf("This is test case 1.\r\n");
    }
    return;
}
/*case2.h*/
#ifndef _CASE2_H
#define _CASE2_H

void case2();
#endif
/*case2.c*/
#include "case2.h"
#include <stdio.h>
#include "test.h"

void case2()
{
    if(testCaseIndex==CASE2_INDEX)
    {
        printf("This is test case 2.\r\n");
    }
    return;
}
/*case3.h*/
#ifndef _CASE3_H
#define _CASE3_H

void case3();
#endif
/*case3.c*/
#include "case3.h"
#include <stdio.h>
#include "test.h"

void case3()
{
    if(testCaseIndex==CASE3_INDEX)
    {
        printf("This is test case 3.\r\n");
    }
    return;
}
/*test.h*/
#ifndef _TEST_H
#define _TEST_H
#define CASE1_INDEX  (100100)
#define CASE2_INDEX  (100200)
#define CASE3_INDEX  (100300)

extern unsigned int testCaseIndex;
extern char StringCmd[];
void testcaseSelect();
#endif
/*test.c*/
#include "test.h"
#include "case1.h"
#include "case2.h"
#include "case3.h"
unsigned int testCaseIndex=0;
#define STRING_CMD_SIZE (256)
char StringCmd[STRING_CMD_SIZE];
void testcaseSelect()
{
    case1();
    case2();
    case3();
}

     下面我们看一下 m a i n main main函数:

  • 在循环中首先接收串口上位机发来的我们上面提到的格式的字符串( r e c e i v e T e s t S t r i n g ( ) ; receiveTestString(); receiveTestString();
  • 然后获取整数值参数( t e s t C a s e I n d e x = g e t N u m b e r P a r a m e t e r ( " c a s e " , 0 ) ; testCaseIndex=getNumberParameter("case",0); testCaseIndex=getNumberParameter("case",0);)和字符串参数( g e t S t r i n g P a r a m e t e r ( " c o m m a n d " , S t r i n g C m d ) ; getStringParameter("command", StringCmd); getStringParameter("command",StringCmd);)
  • 最后根据获取的参数值调用特定的测例。
  • 对接收字符串的 B U F F E R BUFFER BUFFER重新初始化后重复以上过程来调用不同的测例。
#include "message.h"
#include "usart.h"   
#include "test.h"
int main(void)
{
    uart_init(USART1,115200);
    initTestStringBuffer();
    while(1)
    {    
        receiveTestString();
        testCaseIndex=getNumberParameter("case",0);
        getStringParameter("command", StringCmd);
	    printf("StringCmd=%s.\r\n",StringCmd);
        testcaseSelect();
        initTestStringBuffer();
        testCaseIndex=0;
    }         
    return 1;
}

     以上只是提供了一个简单的思路和模版,自己可以根据需求增加其他内容。我的工程在这里

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qqssss121dfd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值