基于STM32战舰开发板的USMART调试组件的使用

USMART调试组件是什么?

USMART是正点原子团队为其STM32开发平台开发的一种类似linux的shell的调试工具。具体工作过程是通过串口发送命令给单片机,然后单片机收到命令之后调用单片机里面对应的相关函数,并执行,同时支持返回结果。

USMART是干什么的?

USMART的功能就是改变了函数参数修改的方式,我们以前总是“修改函数参数->下载到开发板中执行”,但是使用USMART之后,我们不用在那样做了,而是“用串口进行函数参数的修改->开发板执行相应操作”。

在我看来,USMART就是一个函数调试助手,可以从串口发送参数也可以从串口接收函数返回值。

USMART的作用机理

这里我们要明白一下,函数的存储方式:

基于STM32战舰开发板的USMART调试组件的使用-编程知识网

 

其中,函数体是一个数据处理的工具本身并不占用空间,只是在调用函数处理数据的时候在进行压栈出栈的一些逻辑操作。

USMART修改这些函数的参数值正是通过“访问函数参数的地址并且修改这些参数“实现的。

USMART调试功能的实现——定时器与串口的配合使用

基于STM32战舰开发板的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调试组件的执行流程

基于STM32战舰开发板的USMART调试组件的使用-编程知识网

 

USMART使用过程中的疑难

有返回值的函数在执行后,会通过串口返回函数返回值吗?

  1. //usamrt执行函数  
  2. //该函数用于最终执行从串口收到的有效函数.  
  3. //最多支持10个参数的函数,更多的参数支持也很容易实现.不过用的很少.一般5个左右的参数的函数已经很少见了.  
  4. //该函数会在串口打印执行情况.:"函数名(参数1,参数2…参数N)=返回值".的形式打印.  
  5. //当所执行的函数没有返回值的时候,所打印的返回值是一个无意义的数据.  
  6. void usmart_exe(void)  
  7. {  
  8.     u8 id,i;  
  9.     u32 res;             
  10.     u32 temp[MAX_PARM];//参数转换,使之支持了字符串   
  11.     u8 sfname[MAX_FNAME_LEN];//存放本地函数名  
  12.     u8 pnum,rval;  
  13.     id=usmart_dev.id;  
  14.     if(id>=usmart_dev.fnum)return;//不执行.  
  15.     usmart_get_fname((u8*)usmart_dev.funs[id].name,sfname,&pnum,&rval);//得到本地函数名,及参数个数   
  16.     printf("\r\n%s(",sfname);//输出正要执行的函数名  
  17.     for(i=0;i<pnum;i++)//输出参数  
  18.     {  
  19.         if(usmart_dev.parmtype&(1<<i))//参数是字符串  
  20.         {  
  21.             printf("%c",'"');              
  22.             printf("%s",usmart_dev.parm+usmart_get_parmpos(i));  
  23.             printf("%c",'"');  
  24.             temp[i]=(u32)&(usmart_dev.parm[usmart_get_parmpos(i)]);  
  25.         }else                         //参数是数字  
  26.         {  
  27.             temp[i]=*(u32*)(usmart_dev.parm+usmart_get_parmpos(i));  
  28.             if(usmart_dev.sptype==SP_TYPE_DEC)printf("%lu",temp[i]);//10进制参数显示  
  29.             else printf("0X%X",temp[i]);//16进制参数显示       
  30.         }  
  31.         if(i!=pnum-1)printf(",");  
  32.     }  
  33.     printf(")");  
  34.     usmart_reset_runtime(); //计时器清零,开始计时  
  35.     switch(usmart_dev.pnum)  
  36.     {  
  37.         case 0://无参数(void类型)                                                
  38.             res=(*(u32(*)())usmart_dev.funs[id].func)();  
  39.             break;  
  40.         case 1://1个参数  
  41.             res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0]);  
  42.             break;  
  43.         case 2://2个参数  
  44.             res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1]);  
  45.             break;  
  46.         case 3://3个参数  
  47.             res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2]);  
  48.             break;  
  49.         case 4://4个参数  
  50.             res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3]);  
  51.             break;  
  52.         case 5://5个参数  
  53.             res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3],temp[4]);  
  54.             break;  
  55.         case 6://6个参数  
  56.             res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3],temp[4],\  
  57.             temp[5]);  
  58.             break;  
  59.         case 7://7个参数  
  60.             res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3],temp[4],\  
  61.             temp[5],temp[6]);  
  62.             break;  
  63.         case 8://8个参数  
  64.             res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3],temp[4],\  
  65.             temp[5],temp[6],temp[7]);  
  66.             break;  
  67.         case 9://9个参数  
  68.             res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3],temp[4],\  
  69.             temp[5],temp[6],temp[7],temp[8]);  
  70.             break;  
  71.         case 10://10个参数  
  72.             res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3],temp[4],\  
  73.             temp[5],temp[6],temp[7],temp[8],temp[9]);  
  74.             break;  
  75.     }  
  76.     usmart_get_runtime();//获取函数执行时间  
  77.     if(rval==1)//需要返回值.  
  78.     {  
  79.         if(usmart_dev.sptype==SP_TYPE_DEC)printf("=%lu;\r\n",res);//输出执行结果(10进制参数显示)  
  80.         else printf("=0X%X;\r\n",res);//输出执行结果(16进制参数显示)         
  81.     }else printf(";\r\n");      //不需要返回值,直接输出结束  
  82.     if(usmart_dev.runtimeflag)  //需要显示函数执行时间  
  83.     {   
  84.         printf("Function Run Time:%d.%1dms\r\n",usmart_dev.runtime/10,usmart_dev.runtime%10);//打印函数执行时间   
  85.     }     
  86. }  

我们看到串口执行函数的最后几行,清晰的表明“如果函数需要返回返回值,就通过串口进行打印”。

我们调试函数的结果时,出现的结果会一闪而过?

这很正常,由于我们的串口扫描执行函数是在定时器中断服务函数中执行的,因此当我们执行完中断服务函数后,要回到原来执行的主程序的位置继续执行。这样才会有“出现的结果一闪而过”的现象,为了更清晰的看到函数执行的结果(例如:显示屏显示的图像),我们可以使用delay延迟函数。

注意:你看到的一闪而过的结果是因为,中断执行完成后,主程序中的程序运行的结果覆盖了你在中断服务函数中运行的结果。

USMART功能占用了STM32的引脚资源

基于STM32战舰开发板的USMART调试组件的使用-编程知识网

 

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)”指令:

基于STM32战舰开发板的USMART调试组件的使用-编程知识网

 

有如下原理图可知,当我们PB5输出低电平后,LED小灯被点亮:

 

基于STM32战舰开发板的USMART调试组件的使用-编程知识网

基于STM32战舰开发板的USMART调试组件的使用-编程知识网