嵌入式开发<单片机软件调试>
前言
本人从事单片机软件设计工作多年,既从事过裸机系统的设计,也在小型嵌入式实时操作系统下进行过设计。在这些年的开发工作中,对产品设计逐步形成了一定的认识,并进行了较多的总结。因此,一直以来,都想整理这些东西,希望对有需要的人能提供一些帮助或参考。
在诸多的想法中进行系统的整理其实还是有一定的工作量。目前来说,也只能一点一点的完成。之前本人已经对一些较普通的驱动进行了整理,并进行了优化,感兴趣的朋友可以前去看看(其中包含完整代码并已经调试通过)。
其实,在产品设计中涉及到诸多方面,比如产品的信息与参数,产品的调试信息与运行状态数据(甚至形成关键日志),产品的软件升级方法等等。这些东西应该形成一个统一的约定,便于公司及开发同事形成共同的方法,对于设计与维护都会有益。关于产品的信息与参数这方面的内容,我在之前的嵌入式调试工具(串口调试工具与网络调试工具中有详细说明)。
当然,这些东西太多,肯定不会一股脑的写出来。这次的主题是软件调试,而在这里主要讲的是交互式调试,其可用于修改产品信息与参数,查看运行状态数据等等。可使得产品在无任何输入输出接口时,仍然可以进行交互操作。并且,其使用接口可以是普通的串口(RS232),也可以是RS485,以太网,无线网络,只要能与电脑通信方便,对使用的接口原则上没有限制。
另外,还有一点。我们设计的以单片机为主控的产品,其实有很多是没有输入输出接口的,其调试与测试都是很困难的,甚至让人感觉无从下手。因此,设计交互式调试方法,就使得这方面的工作变得非常容易,所以,这也是这个工作有必要和有意义的体现。
一、交互式调试设计依赖工具
原则上来讲,所依赖的工具没有特别要求,只要能找一个相应接口的调试工具即可。比如RS232与RS485接口就可以使用普通的串口调试助手;使用网络接口,则可以使用网络调试助手。当然,有条件的情况下,你或者你的公司可以开发一款适用于自己的调试工具,它可以是专用的,也可以是通用的。
本人就根据自己及公司产品特点以及软件开发的习惯,自行开发了两款通用调试工具,即串口调试工具与网络调试工具,二者除网络通信方式相关内容不同外,其余几乎一致,也很容易使用。并且目前已经形成公司产品开发调试及生产和维护不可缺少的工具。
二、交互式调试设计的使用方法
以下均以串口调试工具(或串口助手)为例
1.普通方法
在串口工具中,一般有发送区和接收区。交互调试,首先得通过串口给设备发送命令字符串。因此,其交互过程为:
1) 在发送区,输入自定义的命令字符串(或带参数);
2) 设备接收后进行命令比较,如未能查找到此命令,则不响应或提示错误;
3) 如能查找到命令,则执行其要求的操作;
4) 适当的在接收区输出操作结果;
5) 如当前命令仅仅启动一个操作,并且后续仍然有交互操作,则需要按照上面的类似步骤继续执行,直至整个功能执行完毕;
以上方法,存在诸多问题,使得实际使用时并不方便。其缺点体现在以下几个方面:
A, 不容易记住:
如果公司没有进行统一规定,则每一个开发工程师可能对同一个功能会使用不同的方式,甚至有些人会使用中文字符串。如时间久了,可能开发的工程师都不记得了。
即便公司进行了统一规定,记住这些命令也是不容易的,因此会制作一个命令表格,在使用时需要调出此表格,并进行复制与粘贴操作。
无论如何,这种输入字符串的方式是非常不方便的,也制约了这种交互式操作的便利性。
B, 不容易使用:
这里的不容易使用,已经不特指是否能记住的问题。而是指此命令可能不知道应该如何使用。比如,是否需要参数数据,甚至参数数据的格式如何等等,也就是使用方法的问题。
当然,公司也可以在指定命令表时,将使用方法即参数定义等等写在一起。但与上一个缺点有一个共性的地方就是,当用户在使用时,它并没有这个命令表,可能因为某种原因会导致他需要暂停维护工作。
C, 存在使用风险:
在设计的众多操作命令中,可能会存在一些风险性较高的操作。比如恢复出厂设置、删除数据、格式化磁盘等等。有时候可能你确实需要执行这个操作,但有时候又难免你是误操作。
如果没有进行有效拦截,并进行确认,这可能会造成灾难性的后果。当然,你可以在应用程序中对操作的用户进行提示,但万一某一个工程师忘记这样做了呢,所以这并不是一个好的方法。
D, 其它问题:
其它的问题虽然不是致命的,或严重的,但也可能存在不方便的地方。比如,设计的命令当中肯定有些是不希望生产、测试、客户使用的。可能仅仅是我们开发人员自己的一个测试命令。而且,即便是生产、测试、维护他们能使用的命令也不一定相同,可能有些命令就是不给维护使用,有些命令也不给生产使用。其实,对于上面提到的问题,也就是命令分类的问题。当然,我们也可以在命令表里面进行分类,然后给到客户,但这也是一件很麻烦的事情。
还有,比如测试人员,在测试的过程中发现了一个BUG,但可能他根本记不住之前进行过什么操作。如果能完整的记录其操作过程,对于解决BUG是有很大帮助的。
当然还有一些其它的问题,这里不再一一列出。
2.定制方法
这里提到的定制方法,即本人使用的方法。首先,本人开发了两款相似的调试工具(上面已经提到),其主要核心是解决了操作命令的相关问题。针对以上问题而设计的主要功能为功能按钮,其具有的特点如下:
1) 提供功能按钮页面
在功能按钮页面中,可执行功能按钮导入、导出、删除。
2) 保存功能按钮与选择
另外,可在接收区执行功能按钮的保存。这需要接收区的文本信息满足一定的格式,这样工具软件可以进行识别。
同时,因为这个保存功能,可以在用户产品中设计输出满足格式的功能按钮文本,这样用户在任何地点使用均可以调出按钮文本并进行保存。
保存的功能按钮被保留在本地硬盘上,通过一个下拉菜单,用户可以选择已经保存的按钮功能文件,并调出其功能按钮执行相应的操作。
3) 功能按钮的核心内容
功能按钮包含诸多元素,使其执行测试操作非常方便,其主要内容如下:
A, 按钮名称:
这个以顾名思义的方式由设计者进行命名,可以中午也可以英文,只要能表达其执行的功能即可。只是其名称被限定在8个中文字符(16个英文字符)内;
B, 按钮功能:
实际上就是操作命令,即用户点击此按钮后,通过发送区发送到产品的命令字符串,当然这个也是由设计者进行自定义的,并且其定义的字符串完全可以不需要公司进行约定。因为这个命令与操作的用户无关,用户不再需要记住此命令;
C, 按钮确认:
指示用户点击此按钮时,是否弹出确认操作的提示框,此设置可避免用户误操作。
D, 按钮参数:
指示在执行此命令时,是否必须在发送区输入命令参数。如果设置为Y,则工具软件在执行此按钮时将检查发送区是否有参数数据,并在没有参数数据时拒绝执行此命令,并弹出提示窗口(如果有按钮参数说明提示性内容,则一并输出);
另外,此项还兼具另一个功能,当其被设置为H时,则指示按钮功能内容为HEX数据,而非ASCII字符。
E, 提示信息:
这是一个可选项,当这个按钮必须输入参数数据时,应该设置此项。主要以字符串方式填写此功能按钮的说明以及参数数据的使用说明等等。
这里首先列出功能按钮文本格式
按钮文件:ZGLJ-C2-new调试按钮
=============================================
按钮名称 按钮命令 确认 参数 提示信息
------------------------------------------------------------
{重启设备} {C-RESTARTS} {N} {N} {}
{恢复出厂设置} {C-SETFACTORY} {Y} {N} {将设备基本信息、工作参数及通信参数恢复到默认值}
=============================================
以上主要介绍的是交互调试的使用方法,并重点介绍了本人开发与之配合使用的工具。如果你感兴趣,可以在最后的链接中去下载并使用。当然,你也可以自行开发或者使用普通工具软件。不过,如果你使用的是普通串口助手或网络助手,可能达不到此文所讲的目的。
下面将重点介绍产品设备中如何进行相应的设计。
三、交互式调试产品设计
上文提到,我设计的工具软件具备一个重要功能,即可以通过接收区的文本信息进行按钮文本保存。这是一个非常重要的功能(当然,你为了不设计这个功能,可以按照前面介绍的按钮文本格式自行编辑)。不过,我仍然建议在你的产品中具备这个功能,因为它确实是非常的方便。
1, 全局需要使用的宏定义
#define getStrAddr(str) (char*)(&str)
#define GetDebugCmd(BuffStr,CmdStr) (strncmp(BuffStr, CmdStr, strlen(CmdStr))==0)
#define UNUSED(x) x=x
#define buttonMax 18
#define buttonFiles 3
以下分别说明其作用:
1)getStrAddr获取FLASH中存储的字符串地址;
2)GetDebugCmd比较输入字符串与命令字符串;
3)UNUSED参数未使用(避免警告信息);
4)buttonMax设计的功能按钮总数;
5)buttonFiles输出的功能按钮文本总数;
2,设计一个功能按钮表格
说明:设计的这个表格,可以放在芯片的FLASH中,不必存储在RAM中。
1) 功能按钮数据结构
typedef void (*pButtonFunc)(tSerialBuff *);
这是功能按钮对应函数原型,其中tSerialBuff结构为本人设计的串口驱动缓存,在此不必关心。
typedef struct
{
char *pButtonName; //按钮名称字符串指针
char *pButtonFunc; //按钮功能字符串指针
bool ensure; //按钮确认
bool param; //按钮参数
u8 attrib; //按钮属性
char *pPrompt; //按钮提示信息字符串指针
pButtonFunc pFunc; //按钮功能函数
}tButton;
其中attrib(按钮属性)被定义为以下内容:
#define BUTTON_NONE 0x00
#define BUTTON_DEBUG 0x01 //调试按钮
#define BUTTON_TEST 0x02 //测试按钮
#define BUTTON_CUSTOMER 0x04 //用户按钮
#define BUTTON_FACTORY (BUTTON_DEBUG|BUTTON_TEST)
#define BUTTON_ALL (BUTTON_DEBUG|BUTTON_TEST|BUTTON_CUSTOMER)
2) 设计按钮表格
这里将提供一个本人最近设计项目按钮表格的完整设计,其中说明部分是方便移植以及维护使用,并且在每一个按钮定义时也添加了title信息,方便对应填写内容。
另外,工具软件中功能按钮是分页显示的,可选择上一页及下一页(如果有那么多按钮)。并且每一页有20个按钮,且其分别为右上角有2个,下面分3排,每一排有6个,所以可以看到在定义时是以此界面进行布局的。为了方便理解,这里给出一张已经在工具软件上生成的功能按钮截图。
以下是按钮表格
const tButton button[buttonMax+buttonFiles]=
{ /****************************************************************************************************************
说明:
1,按钮文件字符串中不可以使用'{' 及'}'这两个符号;
2,按钮名称:即显示在按钮上的文字,应该限制在8个中文字符(16个英文字符)内;
3,按钮功能:点击按钮时发送的字符串命令,限制与按钮名称相同,但不强制;
4,按钮确认:表示用户点击时,是否提示用户确认;
5,按钮参数:表示用户点击时,是否检测有输入参数,并且在无输入参数时拒绝执行;
6,按钮属性:用于屏蔽获取按钮输出(可以有3种按钮类型);
7,按钮提示信息:右键点击按钮可获取按钮功能或参数说明;
8,按钮功能函数:点击按钮时的响应功能函数;
*****************************************************************************************************************/
//第一页
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
getStrAddr("重启设备"), getStrAddr(DBG_CMD_RESTARTS),
FALSE,FALSE,BUTTON_ALL,NULL,DebugParse_Restart
},
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
getStrAddr("恢复出厂设置"), getStrAddr(DBG_CMD_RESETFACTORY),
TRUE,FALSE,BUTTON_ALL,
getStrAddr("将设备基本信息、工作参数及通信参数恢复到默认值"),
DebugParse_ResetToFactory
},
//第一页--第一行
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
getStrAddr("读写设备信息"), getStrAddr(DBG_CMD_RWBASEINFO),
FALSE,FALSE,BUTTON_ALL,getStrAddr(RW_PROMPT_MSG),DebugParse_BaseInfo
},
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
getStrAddr("读写工作参数"), getStrAddr(DBG_CMD_RWWORKPARAM),
FALSE,FALSE,BUTTON_ALL,getStrAddr(RW_PROMPT_MSG),DebugParse_WorkParam
},
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
getStrAddr("读写通信参数"), getStrAddr(DBG_CMD_RWCOMMPARAM),
FALSE,FALSE,BUTTON_ALL,getStrAddr(RW_PROMPT_MSG),DebugParse_CommParam
},
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
getStrAddr("读取设备状态"), getStrAddr(DBG_CMD_RDDEVSTATUS),
FALSE,FALSE,BUTTON_ALL,NULL,DebugParse_DispDevStatus
},
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
getStrAddr("显示任务状态"), getStrAddr(DBG_CMD_TASKSTATUS),
FALSE,FALSE,BUTTON_DEBUG,NULL,DebugParse_DispTaskRunStatus
},
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
getStrAddr("周期显示任务状态"), getStrAddr(DBG_CMD_TASKSTATUS_PERIO),
FALSE,FALSE,BUTTON_DEBUG,
getStrAddr("打开或关闭每10秒显示一次任务状态"),
DebugParse_DispTaskRunStatusSetup
},
//第一页--第二行
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
getStrAddr("网络数据显示开关"), getStrAddr(DBG_CMD_COMMDATA),
FALSE,FALSE,BUTTON_DEBUG,
getStrAddr("显示或关闭以太网及RS485收发数据\\r\\n"
"1)未输入参数,则同时切换以太网与RS485数据显示开关\\r\\n"
"2)输入参数:ETH,则仅切换以太网数据显示开关\\r\\n"
"3)输入参数:RS485或485,则仅切换RS485数据显示开关"),
DebugParse_SwitchCommDataMsg
},
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
getStrAddr("网络信息开关"), getStrAddr(DBG_CMD_COMMSWITCH),
FALSE,FALSE,BUTTON_DEBUG,
getStrAddr("打开或关闭以太网及RS485调试信息输出\\r\\n"
"1)未输入参数,则同时切换以太网与RS485调试信息显示开关\\r\\n"
"2)输入参数:ETH,则仅切换以太网调试信息显示开关\\r\\n"
"3)输入参数:RS485或485,则仅切换RS485调试信息显示开关"),
DebugParse_SwitchCommMsg
},
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
getStrAddr("信息输出方向控制"), getStrAddr(DBG_CMD_OUTCTRLSWITCH),
FALSE,FALSE,BUTTON_DEBUG,
getStrAddr("控制通信信息输出方向<可选:调试网口及调试串口>\\r\\n"
"1)未输入参数,则同时输出到调试网口与调试串口\\r\\n"
"2)输入参数:ETH,则仅输出到调试网口\\r\\n"
"3)输入参数:RS232或232,则仅输出到调试串口\\r\\n"
"注:调试网口连接时默认输出到调试网口,反之输出到调试串口"),
DebugParse_CommOutCtrl
},
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
getStrAddr("看门狗信息开关"), getStrAddr(DBG_CMD_WATCHDOGSWITCH),
FALSE,FALSE,BUTTON_FACTORY,
getStrAddr("打开开关将用于检测是否收到WAKE信号,并查看执行喂狗操作"),
DebugParse_SwitchWatchDogMsg
},
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
getStrAddr("看门狗喂狗开关"), getStrAddr(DBG_CMD_FEEDDOGSWITCH),
FALSE,FALSE,BUTTON_FACTORY,
getStrAddr("打开开关将用于检测硬件看门狗是否起作用"),
DebugParse_SwitchFeedDog
},
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
getStrAddr("保存参数为默认值"), getStrAddr(DBG_CMD_SETDEFAULT),
TRUE,FALSE,BUTTON_ALL,
getStrAddr("将当前设备基本信息、工作参数及通信参数保存为默认值"),
DebugParse_SetParamToDefault
},
//第一页--第三行
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
getStrAddr("擦除E2PROM"), getStrAddr(DBG_CMD_ERASEE2PROM),
TRUE,FALSE,BUTTON_DEBUG,NULL,
DebugParse_ClearE2prom
},
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
getStrAddr("检测DTU"), getStrAddr(DBG_CMD_DTUID),
FALSE,FALSE,BUTTON_DEBUG,
getStrAddr("用于自动测试中,网络工具自动检测DTUID字段,以便于可自动回复DTUID OK"),
NULL
},
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
getStrAddr("应答DTU"), getStrAddr(DBG_CMD_DTUOK),
FALSE,FALSE,BUTTON_DEBUG,
getStrAddr("用于自动测试或手动测试中,网络工具模拟发送DTUID OK应答信息"),
NULL
},
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
getStrAddr("运行灯闪烁说明"), getStrAddr(DBG_CMD_LEDNOTE),
FALSE,FALSE,BUTTON_FACTORY,
getStrAddr("对LED运行指示灯的各种显示状态做说明"),
DebugParse_LedRunStatusNote
},
//按钮文件获取
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
NULL,getStrAddr(DBG_CMD_BUTTONFILE_DEBUG),
FALSE,FALSE,BUTTON_NONE,NULL,DebugParse_ButtonFile
},
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
NULL,getStrAddr(DBG_CMD_BUTTONFILE_TEST),
FALSE,FALSE,BUTTON_NONE,NULL,DebugParse_ButtonFile
},
{ //按钮名称/按钮功能/按钮确认/按钮参数/按钮属性/按钮提示信息/按钮功能函数
NULL,getStrAddr(DBG_CMD_BUTTONFILE_CUSTOMER),
FALSE,FALSE,BUTTON_NONE,NULL,DebugParse_ButtonFile
},
};
3, 设计按钮功能函数
以上按钮表格中,最后一项为按钮功能函数,只需要按照其函数原型设计即可。以下仅列出其中2个用以说明如何设计。
1)一般的按钮功能函数
以下这个功能函数执行设备重启,其是通过向系统任务发送重启事件,而由系统任务完成的,当然在重启设备以前,还会有一些其它工作需要做。
void DebugParse_Restart(tSerialBuff *pRecStr)
{
UNUSED(pRecStr);
osEventGroupSet(pSystem->eventGroupHandle, SYSTEM_EVENTGROUP_RESTART);
vTaskDelay(pdMS_TO_TICKS(500));
}
以下这个功能函数执行设备基本信息读写,其检查是否输入了参数,如果没有参数则直接显示设备基本信息;如果有参数,则调用设置子函数进行解析设置。
之所以列出这个功能函数,我是想说明,在产品设计的过程中,一般会存在一个信息或参数的读和写操作。其实,把这两个功能可以写到一起,并通过是否携带参数决定执行读还是写的功能。我在这类操作中一直如此设计。
void DebugParse_BaseInfo(tSerialBuff *pRecStr)
{
if (pRecStr->length>strlen(DBG_CMD_RWBASEINFO))
DevManager_BaseInfoSet(pRecStr->pBuff);
else
DevManager_DispBaseInfo(TRUE);
}
2)获取文本按钮的功能函数
仔细观察会发现,以上按钮表格中最后3个是一样的功能函数,并且命名为DebugParse_ButtonFile,其执行功能按钮文本输出功能。这个功能函数的调用没有通过按钮执行,而是在发送区输入“获取调试按钮12345678”并发送而获取。其中12345678为密码,这个可以修改,只是解析也需要跟着修改。
DebugParse_ButtonFile这个函数至关重要,其完成3种属性的功能按钮文本输出,并且是与我设计的工具软件要求的格式一致的。
以下列出此函数的代码
void DebugParse_ButtonFile(tSerialBuff *pRecStr)
{
u8 attrib,loop;
char *pOutString;
char spaceStr[16];
u16 offset;
u8 spaceCnt;
//获取调试/测试/用户按钮
if (GetDebugCmd(pRecStr->pBuff, DBG_CMD_BUTTONFILE_DEBUG))
attrib=BUTTON_DEBUG;
else if (GetDebugCmd(pRecStr->pBuff, DBG_CMD_BUTTONFILE_TEST))
attrib=BUTTON_TEST;
else
attrib=BUTTON_CUSTOMER;
pOutString=(char*)malloc_always(512,0);
memset(pOutString,0,512);
offset=sprintf(pOutString,"\r\n按钮文件:%s-new%s按钮\r\n%s\r\n",
MODEL_NAME,
attrib==BUTTON_DEBUG ? "调试" : (attrib==BUTTON_TEST ? "测试" : "用户"),
BASEINFO_STR_EQU);
offset+=sprintf(&pOutString[offset],"按钮名称 按钮命令 确认 参数 提示信息\r\n%s\r\n",
BASEINFO_STR_SUB);
Comx_PrintStr(DBG_COM,"%s",pOutString);
for (loop=0;loop<buttonMax;loop++)
{
offset=0;
if (button[loop].attrib&attrib)
{
memset(pOutString,0,512);
//填写{按钮名称}
offset+=sprintf(pOutString,"{%s}",button[loop].pButtonName);
if (strlen(button[loop].pButtonName)<=16)
spaceCnt=16-strlen(button[loop].pButtonName);
else
spaceCnt=0;
if (spaceCnt>0)
{
memset(spaceStr,' ',16);
spaceStr[spaceCnt]=0;
offset+=sprintf(&pOutString[offset],"%s",spaceStr);
}
//填写{按钮功能}
offset+=sprintf(&pOutString[offset]," {%s}",button[loop].pButtonFunc);
if (strlen(button[loop].pButtonFunc)<=16)
spaceCnt=16-strlen(button[loop].pButtonFunc);
else
spaceCnt=0;
if (spaceCnt>0)
{
memset(spaceStr,' ',16);
spaceStr[spaceCnt]=0;
offset+=sprintf(&pOutString[offset],"%s",spaceStr);
}
//填写{按钮确认}
offset+=sprintf(&pOutString[offset]," {%c} ",button[loop].ensure==TRUE ? 'Y' : 'N');
//填写{按钮参数}
offset+=sprintf(&pOutString[offset]," {%c} ",button[loop].param==TRUE ? 'Y' : 'N');
//填写{提示信息}
offset+=sprintf(&pOutString[offset]," {%s}\r\n",button[loop].pPrompt==NULL ? "" : button[loop].pPrompt);
Comx_PrintStr(DBG_COM,"%s",pOutString);
}
}
Comx_PrintStr(DBG_COM,"%s\r\n",BASEINFO_STR_EQU);
vPortFree(pOutString);
}
4,设计交互调试命令解析函数
这部分就完全属于交互调试的调用部分,其主要完成输入字符串(交互命令)与功能按钮表格中的每一个按钮功能进行比较,一旦比较一致并且其功能函数存在时(似乎都应该存在),则调用功能函数执行。
以下列出其完整代码。当然,如果你有部分功能需要持续交互,比如在执行过程中输入数据,则可以添加这部分代码在其主体前面,如下面代码中被屏蔽的部分代码。
void DebugParseCommand(tSerialBuff *pRecStr)
{
bool goonCheck=TRUE;
uint8_t loop;
//由于我们沿用了早期的软件升级协议,因此这部分功能没有放在功能按钮表格中,而是单独检测,即下面的DebugParse_Upgrade(pRecStr)代码;
if (pRecStr->length>=8&&memcmp(pRecStr->pBuff, iapUpgradeCmd,8)==0)
DebugParse_Upgrade(pRecStr);
else
{
//以下被屏蔽代码就是执行用户输入数据检测
//goonCheck=DebugParse_UserInput(pRecStr);
//if (goonCheck==TRUE)
{
for (loop=0;loop<(buttonMax+buttonFiles);loop++)
{
if (button[loop].pFunc!=NULL&&GetDebugCmd(pRecStr->pBuff, button[loop].pButtonFunc))
{
button[loop].pFunc(pRecStr);
break;
}
}
}
}
}
5,执行交互调试调用
以上所有的设计完成后,最后就是将DebugParseCommand解析函数放在一个任务中执行。我所设计的大部分代码均使用了FreeRTOS,因此单独建立了一个任务执行。
以下是代码部分,这里的重点是DebugParseCommand函数,其被放在了串口读取完成后执行。
void Debug_Task(void *pvParameters)
{
tSerialBuff recStr;
vTaskDelay(pdMS_TO_TICKS(100));
for(;;)
{
if (Comx_Read(DBG_COM, &recStr, READ_WAITING,0)==OPER_RESULT_OK)
{
DebugParseCommand(&recStr);
Comx_FreeRecBuff(recStr.pBuff);
}
}
}
四,多接口的交互调试设计
最后,扩展说明一下多接口的交互调试设计。我平时遇到的项目中,有些会同时具有多个通信口,比如调试串口(RS232),通信串口(RS485)、通信网口(以太网)等同时存在。
像这种情况,我们设计的交互调试功能,可以让操作人员通过任何一个接口并使用相同的功能按钮执行交互调试。其实,每一种接口在收到数据时,在其本身需要执行的通信任务完成后,尤其是其解析不成功时,可以转向调用DebugParseCommand这个功能函数。
当然,其中关键的设计是调试信息输出函数,其必须设计为可以根据不同配置,调用不同接口的输出函数。在实际执行时,还需要辅助添加输出配置功能,使得每一个接口在收到数据并调用交互调试命令解析函数前将输出配置到需要的接口(当然根据需要可以配置为多个接口同时输出)。
总结
以上内容,写的有些仓促,可能存在没有说清楚或错误的地方,或者你还有更好的方法。无论如何,欢迎提出你宝贵的意见或建议,谢谢。
另外,这里提到了两个调试工具,下面给出链接地址,如果你有需要,欢迎下载使用。
附
如果你感兴趣,可查看其它文档.
单片机软件常用设计分享(一)驱动设计之按键设计
单片机软件常用设计分享(二)驱动设计之LED灯显示设计
单片机软件常用设计分享(三)驱动设计之数码屏显示设计
单片机软件常用设计分析(四)驱动设计之串口驱动设计
嵌入式开发<串口调试工具>
嵌入式开发<网络调试工具>
嵌入式开发<单片机软件升级>