基于STM32战舰开发板的USMART调试组件的使用
USMART调试组件是什么?
USMART是正点原子团队为其STM32开发平台开发的一种类似linux的shell的调试工具。具体工作过程是通过串口发送命令给单片机,然后单片机收到命令之后调用单片机里面对应的相关函数,并执行,同时支持返回结果。
USMART是干什么的?
USMART的功能就是改变了函数参数修改的方式,我们以前总是“修改函数参数->下载到开发板中执行”,但是使用USMART之后,我们不用在那样做了,而是“用串口进行函数参数的修改->开发板执行相应操作”。
在我看来,USMART就是一个函数调试助手,可以从串口发送参数也可以从串口接收函数返回值。
USMART的作用机理
这里我们要明白一下,函数的存储方式:
其中,函数体是一个数据处理的工具本身并不占用空间,只是在调用函数处理数据的时候在进行压栈出栈的一些逻辑操作。
USMART修改这些函数的参数值正是通过“访问函数参数的地址并且修改这些参数“实现的。
USMART调试功能的实现——定时器与串口的配合使用
注意我们这里的串口扫描函数的内容以及串口扫描函数的执行位置:
//usmart扫描函数
//通过调用该函数,实现usmart的各个控制.该函数需要每隔一定时间被调用一次
//以及时执行从串口发过来的各个函数.
//本函数可以在中断里面调用,从而实现自动管理.
//如果非ALIENTEK用户,则USMART_RX_STA和USMART_RX_BUF[]需要用户自己实现
void usmart_scan(void)
{ u8 sta,len; if(USMART_RX_STA&0x8000)//串口接收完成? { len=USMART_RX_STA&0x3fff; //得到此次接收到的数据长度 USMART_RX_BUF[len]='\0'; //在末尾加入结束符. sta=usmart_dev.cmd_rec(USMART_RX_BUF);//得到函数各个信息 if(sta==0)usmart_dev.exe(); //执行函数 else { len=usmart_sys_cmd_exe(USMART_RX_BUF); if(len!=USMART_FUNCERR)sta=len; if(sta) { switch(sta) { case USMART_FUNCERR: printf("函数错误!\r\n"); break; case USMART_PARMERR: printf("参数错误!\r\n"); break; case USMART_PARMOVER: printf("参数太多!\r\n"); break; case USMART_NOFUNCFIND: printf("未找到匹配的函数!\r\n"); break; } } } USMART_RX_STA=0;//状态寄存器清空 }
}
扫描函数的功能其实包含两部分:
① 扫描串口接收的数据;
② 根据扫描结果,执行相应的功能。
//下面这两个函数,非USMART函数,放到这里,仅仅方便移植.
//定时器4中断服务程序
void TIM4_IRQHandler(void)
{ if(TIM_GetITStatus(TIM4,TIM_IT_Update)==SET)//溢出中断 { usmart_dev.scan(); //执行usmart扫描 TIM_SetCounter(TIM4,0); //清空定时器的CNT TIM_SetAutoreload(TIM4,100);//恢复原来的设置 } TIM_ClearITPendingBit(TIM4,TIM_IT_Update); //清除中断标志位
}
在TIMER4的中断服务函数中我们可以看到,当TIM4溢出,即达到1ms时,我们扫描串口并根据接收的命令执行相应的动作。
USMART调试组件的执行流程
USMART使用过程中的疑难
有返回值的函数在执行后,会通过串口返回函数返回值吗?
- //usamrt执行函数
- //该函数用于最终执行从串口收到的有效函数.
- //最多支持10个参数的函数,更多的参数支持也很容易实现.不过用的很少.一般5个左右的参数的函数已经很少见了.
- //该函数会在串口打印执行情况.以:"函数名(参数1,参数2…参数N)=返回值".的形式打印.
- //当所执行的函数没有返回值的时候,所打印的返回值是一个无意义的数据.
- void usmart_exe(void)
- {
- u8 id,i;
- u32 res;
- u32 temp[MAX_PARM];//参数转换,使之支持了字符串
- u8 sfname[MAX_FNAME_LEN];//存放本地函数名
- u8 pnum,rval;
- id=usmart_dev.id;
- if(id>=usmart_dev.fnum)return;//不执行.
- usmart_get_fname((u8*)usmart_dev.funs[id].name,sfname,&pnum,&rval);//得到本地函数名,及参数个数
- printf("\r\n%s(",sfname);//输出正要执行的函数名
- for(i=0;i<pnum;i++)//输出参数
- {
- if(usmart_dev.parmtype&(1<<i))//参数是字符串
- {
- printf("%c",'"');
- printf("%s",usmart_dev.parm+usmart_get_parmpos(i));
- printf("%c",'"');
- temp[i]=(u32)&(usmart_dev.parm[usmart_get_parmpos(i)]);
- }else //参数是数字
- {
- temp[i]=*(u32*)(usmart_dev.parm+usmart_get_parmpos(i));
- if(usmart_dev.sptype==SP_TYPE_DEC)printf("%lu",temp[i]);//10进制参数显示
- else printf("0X%X",temp[i]);//16进制参数显示
- }
- if(i!=pnum-1)printf(",");
- }
- printf(")");
- usmart_reset_runtime(); //计时器清零,开始计时
- switch(usmart_dev.pnum)
- {
- case 0://无参数(void类型)
- res=(*(u32(*)())usmart_dev.funs[id].func)();
- break;
- case 1://有1个参数
- res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0]);
- break;
- case 2://有2个参数
- res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1]);
- break;
- case 3://有3个参数
- res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2]);
- break;
- case 4://有4个参数
- res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3]);
- break;
- case 5://有5个参数
- res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3],temp[4]);
- break;
- case 6://有6个参数
- res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3],temp[4],\
- temp[5]);
- break;
- case 7://有7个参数
- res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3],temp[4],\
- temp[5],temp[6]);
- break;
- case 8://有8个参数
- res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3],temp[4],\
- temp[5],temp[6],temp[7]);
- break;
- case 9://有9个参数
- res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3],temp[4],\
- temp[5],temp[6],temp[7],temp[8]);
- break;
- case 10://有10个参数
- res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3],temp[4],\
- temp[5],temp[6],temp[7],temp[8],temp[9]);
- break;
- }
- usmart_get_runtime();//获取函数执行时间
- if(rval==1)//需要返回值.
- {
- if(usmart_dev.sptype==SP_TYPE_DEC)printf("=%lu;\r\n",res);//输出执行结果(10进制参数显示)
- else printf("=0X%X;\r\n",res);//输出执行结果(16进制参数显示)
- }else printf(";\r\n"); //不需要返回值,直接输出结束
- if(usmart_dev.runtimeflag) //需要显示函数执行时间
- {
- printf("Function Run Time:%d.%1dms\r\n",usmart_dev.runtime/10,usmart_dev.runtime%10);//打印函数执行时间
- }
- }
我们看到串口执行函数的最后几行,清晰的表明“如果函数需要返回返回值,就通过串口进行打印”。
我们调试函数的结果时,出现的结果会一闪而过?
这很正常,由于我们的串口扫描执行函数是在定时器中断服务函数中执行的,因此当我们执行完中断服务函数后,要回到原来执行的主程序的位置继续执行。这样才会有“出现的结果一闪而过”的现象,为了更清晰的看到函数执行的结果(例如:显示屏显示的图像),我们可以使用delay延迟函数。
注意:你看到的一闪而过的结果是因为,中断执行完成后,主程序中的程序运行的结果覆盖了你在中断服务函数中运行的结果。
USMART功能占用了STM32的引脚资源
USMART配置流程
步骤一 |
主函数中调用usmart_dev.init函数初始化usmart |
步骤二 |
在usmart_config.h中注册函数 |
初始化函数的调用格式:
uart_init(115200); //串口初始化为115200
usmart_dev.init(SystemCoreClock/1000000); //初始化USMART
正点原子封装的USMART是在其封装的USART串口通信源码基础上建立的,因此我们必须先初始化USART然后再调用usmart_dev.init(时钟频率)初始化定时器时钟频率,其实usmart_dev.init是usmart_init()函数的函数指针,我们可以参考usmart_init()代码:
void usmart_init(u8 sysclk)
{
#if USMART_ENTIMX_SCAN==1 Timer4_Init(1000,(u32)sysclk*100-1);//分频,时钟为10K ,100ms中断一次,注意,计数频率必须为10Khz,以和runtime单位(0.1ms)同步.
#endif usmart_dev.sptype=1; //十六进制显示参数
}
注册函数的格式:
(void*)LCD_ReadPoint,"u16 LCD_ReadPoint(u16 x,u16 y)"
// (void*)函数名,”函数声明”,
这样的声明形式也正应验了我们之前所提及的USMART作用的实质:
① |
通过串口接收数据 |
② |
访问“函数首地址+传入函数对应参数地址偏移量” |
③ |
执行该函数 |
USMART代码示例
Led.c
#include "stm32f10x.h"
#include "led.h" void led_set(u8 state)
{ GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB,&GPIO_InitStructure); PBout(5) = state;
}
Led.h
#ifndef _LED_H
#define _LED_H #include "sys.h" void led_set(u8 state); #endif
Usmart_config.c
// 将这个程序替换掉原来的usmart_config.c程序即可
#include "usmart.h"
#include "usmart_str.h" 用户配置区///
//这下面要包含所用到的函数所申明的头文件(用户自己添加)
#include "delay.h"
#include "sys.h"
#include "led.h" extern void led_set(u8 state); //函数名列表初始化(用户自己添加)
//用户直接在这里输入要执行的函数名及其查找串
struct _m_usmart_nametab usmart_nametab[]=
{
#if USMART_USE_WRFUNS==1 //如果使能了读写操作 (void*)read_addr,"u32 read_addr(u32 addr)", (void*)write_addr,"void write_addr(u32 addr,u32 val)",
#endif (void*)delay_ms,"void delay_ms(u16 nms)", (void*)delay_us,"void delay_us(u32 nus)", (void*)led_set,"void led_set(u8 state)",
};
///END///
/
//函数控制管理器初始化
//得到各个受控函数的名字
//得到函数总数量
struct _m_usmart_dev usmart_dev=
{ usmart_nametab, usmart_init, usmart_cmd_rec, usmart_exe, usmart_scan, sizeof(usmart_nametab)/sizeof(struct _m_usmart_nametab),//函数数量 0, //参数数量 0, //函数ID 1, //参数显示类型,0,10进制;1,16进制 0, //参数类型.bitx:,0,数字;1,字符串 0, //每个参数的长度暂存表,需要MAX_PARM个0初始化 0, //函数的参数,需要PARM_LEN个0初始化
};
Main.c
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
#include "usmart.h"
#include "usmart_str.h"
#include "usart.h" int main()
{ led_set(1); uart_init(115200); // 初始化USART1 usmart_dev.init(SystemCoreClock); // _m_usmart_dev结构体的对象是usmart_dev,在usmart.h中声明 while(1);
}
运行结果展示
通过串口向单片机发送“led_set(0)”指令:
有如下原理图可知,当我们PB5输出低电平后,LED小灯被点亮: