嵌入式软件架构设计之六:通过UART串口-构建“AT命令”处理机

       关键词:AT命令、AT命令处理机、单向链表、函数指针、匹配分发

        上一章我们介绍了通过UART串口,构建一个解析处理“命令行”输入的方法,由于比较类似,趁热打铁,我们再介绍一下处理“AT命令”的方法,因为他们的处理流程是一样的,只是具体的解析有所差异,"AT命令"在IOT领域目前有着广泛的应用,诸如2G/3G/4G通信模组、WIFI模组、蓝牙模组、Lora功能模组、UWB功能模组以及其他很多终端设备都使用AT命令与外部进行交互通信,所以有必要介绍一下。

AT命令的历史由来

       AT命令是AT指令集的简称,AT 是英文Attention的缩写,AT命令的特点是每条命令均以字母"AT"开头,"AT"后面再跟上不同的符号、字母、数字来表明不同的功能,并以\n为结束的一套文本指令集。AT指令最早由美国贺氏公司发明并应用于其1981年研制的贺氏智能调制解调器(Hayes Smartmodem)中,计算机可以通过这些命令来控制MODEM实现摘机、拨号、挂机等操作,所以AT 命令集也称Hayses 指令集(Hayes command set)。随着技术的进步,贺氏公司和其推出的低速拨号MODEM 早已退出历史舞台,但他们开发的AT命令因其方便实用的特点被保留并沿用下来。在第2代移动通信时代,当时移动通信领域主要的移动电话生产厂商诺基亚、爱立信、摩托罗拉和HP共同为GSM 研制了一整套 AT命令,作为控制GSM通信模块的通信协议,AT命令在此基础上继续扩充完善并加入GSM 07.05 标准以及后来的 GSM 07.07 标准,使得AT命令作为一种标准通信协议得以广泛应用。随后的GPRS/3G/4G时代AT命令继续发挥着重要的作用,在物联网时代的今天,AT命令广泛的使用在嵌入式物联网开发领域,不仅在传统的蜂窝模块的应用上,在诸如WIFI、蓝牙、NFC等其他物联网接入的开发上,AT命令作为一种通用控制协议也得到广泛使用。

       我们先来介绍一下AT命令,完整的AT命令分为两部分,AT请求和AT响应。

AT命令语句

                         

        AT命令是一种文本命令,即字符串命令,由请求端发送AT命令请求,接收端接收AT命令,处理并作出响应。从AT命令请求响应的角色划分,我们把AT命令请求端称作AT命令client,把AT命令接收处理的一端称作AT Server。在通信领域,请求方一般是DCE(数据通信设备),接收方是DTE(数据终端设备)。AT命令语句类型通常有以下三种:AT命令请求、AT命令响应、未经请求的主动上报URC。详见下:

AT命令请求

       AT命令有固定的语法格式,每条命令必须以前缀AT或at为开头,以<CR>回车符为命令行终止。AT命令的响应形式为:<CR><LF><response><CR><LF> 。为表述方便,有时我们会省略<CR><LF>,仅显示命令和响应内容。

通常AT命令可以在语法上分为三类:基础类、S参数类和扩展类,如下所列。

  • 基础类

       基础类AT命令的格式为AT<x><n>或AT&<x><n>,其中<x>是命令,<n>是该命令的参数。常见的AT<x><n>格式命令如下:

命令功能示例
ATD<电话号码>拨号ATD1502955xxxx(拨打电话1502955xxxx)
ATA应答来电ATA(手动应答)
ATH挂断ATH(挂断当前连接)
ATE<n>回显设置ATE0(关闭回显);ATE1(开回显)  

常见的AT&<x><n>格式命令如下:

其中 & - 表示硬件相关的设置;<x> - 单字母参数类别标识符;<n> - 数字参数值(通常0或1)

命令功能示例
AT&RnRTS/CTS流控AT&R0 禁用流控;AT&R1 使能流控
AT&Fn恢复预设配置AT&F0(恢复出厂设置)
  • S参数类

此类AT命令格式有ATS<n>=<m>,其中<n>是将要设置的S寄存器的索引,<m>是赋予的参数
值。

  • 扩展类

扩展类AT命令可以在多种模式下运行,如下表所示:

扩展类AT命令类型                语句                                                                                              说明

测试命令                           AT+<cmd>=?                                                         测试是否存在相应的命令,并返回有关其

                                                                                                                         参数的 类型、值或范围的信息。                  

查询命令                            AT+<cmd>?                                                           查询相应命令的当前参数值。
设置命令                            AT+<cmd>=<p1>[,<p2>[,<p3>[...]]]                        设置用户可定义的参数值。
执行命令                            AT+<cmd>                                                              返回特定的参数信息或执行特定的操作。 

AT命令响应

         AT命令的响应由两部分组成,一部分是本条命令的响应消息,一部分是最终的响应结果,成功会返回OK;失败或错误返回ERROR或+CME ERROR:<err>。                                                                当AT命令Server处理完一条命令,会发送请求的响应消息,并会返回OK、ERROR或+CME ERROR: <err>作为响应结束,表示本条命令全部处理完成,并准备好接收下一条新命令。 
以下是响应消息的格式:  
<CR><LF>+CMD: <parameters><CR><LF> 
<CR><LF>OK<CR><LF>  
或者 
<CR><LF><parameters><CR><LF> 
<CR><LF>OK<CR><LF>

AT命令未经请求的结果码URC

       AT server端的一些状态变化、事件消息等需要主动上报或通知给AT client时会通过未经请求的结果码URC形式进行发送。也就是说这类上报不需要client请求,属于Server端的自发的主动上报行为,主要由某些事件触发上报,如网络断连、短信接收、来电提醒。另外需要注意的是虽然URC一般由相应的关键字作区分提示,但是server端还是要尽量避免将URC上报同AT响应混合在一起进行响应,避免client误将URC作为响应造成处理错误。

构建AT命令处理机

       经过上面的介绍大家都对AT命令有了一定的了解,我们这里就介绍一种构建扩展类的AT命令的处理机的方法,使系统可以和外部进行交互,对外部的AT请求进行响应。如上所述,

命令结构元素

  • AT   - 命令前缀(Attention)

  • 名称- 基础指令中的"D"、"H"或“&F”,S指令中的"S",扩展类指令中的“+<cmd>”均视作命令名

  • 符号-等号、问号、等号问号组合

  • 参数-分无参、单个参数、多个参数

根据一条AT命令中是否包含以上元素,我们将AT命令进行分类如下:

(1)无名称  如命令AT,单独一个前缀

(2)只有名称,无参数,无符号,如基础指令:ATA,扩展指令AT+<cmd>       

  (3)有名称,有参数,如基础指令:ATE<n>    

  (4)有名称和问号,如扩展指令AT+<cmd>?

  (5)有名称和等号、参数,如扩展指令AT+<cmd>=<p1>[,<p2>[,<p3>[...]]]   

  (6)有名称、等号和问号,如扩展指令AT+<cmd>=?   

根据以上分析,我们构建命令结构体如下:

typedef struct
{
  uint32_t element;
  char     name[AT_CMD_NAME_MAX_LEN];
  uint32_t parameter_num;
  parameter_t *p_para_line;
}AtCmd_t;

        其中成员element记录了该命令的构成,即由元素名称、等号、问号、参数等构成的不同组合,用于判断命令格式;name存储命令名称,parameter_num记录参数个数,p_para_line是一个链表结构,将参数通过单向链表的形式连接起来,可以满足多个参数的需求。

解析AT命令字符串,将解析结果记录到AtCmd_t结构体中:

static int at_cmd_decode(char *at_cmd,uint16_t cmd_len)
{  
   uint16_t i=0; 
   char *pat_cmd=NULL;

   if(at_cmd==NULL||cmd_len<2)
   {	
      printf("\r\nERROR\r\n");
      return -1;
   }

   for(i=1;i<=cmd_len;i++)
   {
     if( (at_cmd[i-1]=='a'||at_cmd[i-1]=='A')&&(at_cmd[i]=='t'||at_cmd[i]=='T')  )
     {       
       pat_cmd=at_cmd+i-1;
	   break;
	 }
   }
  
   if(pat_cmd==NULL)
   {	
      printf("ERROR:no 'AT' found\r\n");
      return -1;
   }
  
   if(cmd_len==2)//'AT'
   {
      g_at_cmd.element=0x00;
   }
   else if('&'==pat_cmd[2])//&参数类指令解析:AT&<x><n>
   {
      char para[8];
	  
      g_at_cmd.element=ELEM_NAME|ELEM_PARAMETER;
	  g_at_cmd.name[0]='&';
	  g_at_cmd.name[1]=pat_cmd[3];
	  g_at_cmd.name[2]='\0';
	  
	  para[0]=pat_cmd[4];
	  para[1]='\0';
	  
	  insert_at_cmd_parameter(para);
   }
   else if('S'==pat_cmd[2])//S参数类指令解析:ATS<n>=<m>
   {
      char *eq=strstr((char *)pat_cmd,"=");
   	  if(eq)
      {      
		  char n_str[20], m_str[20];
		  int n,m;
          g_at_cmd.element=ELEM_NAME|ELEM_EQUAL_SIGN|ELEM_PARAMETER;
	      g_at_cmd.name[0]='S';
	      g_at_cmd.name[1]='\0';

		 memset(n_str,'\0',sizeof(n_str));
		 memset(m_str,'\0',sizeof(m_str));
		  
	     if (sscanf(pat_cmd,"ATS%d=%d",&n,&m) == 2) 
		 {
	        sprintf(n_str,"%d",n);
			sprintf(m_str,"%d",m);
	        insert_at_cmd_parameter(n_str);
			insert_at_cmd_parameter(m_str);
         } 
		 else 
		 {
            printf("sscanf error\n");
         }     
	  }	
   }
   else if('+'==pat_cmd[2])//扩展指令解析
   {
        if(strstr((char *)pat_cmd,"?"))
        {
           if(strstr((char *)pat_cmd,"="))
           {
              g_at_cmd.element=ELEM_NAME|ELEM_QUESTION_MARK|ELEM_EQUAL_SIGN;
	          if(get_extend_at_cmd_name(pat_cmd,cmd_len,'=',g_at_cmd.name))
	          {
	            printf("=? err\r\n");
                return -1;
		      }
	       }
	       else
	       {
               g_at_cmd.element=ELEM_NAME|ELEM_QUESTION_MARK;
	           if(get_extend_at_cmd_name(pat_cmd,cmd_len,'?',g_at_cmd.name))
	           {
	            printf("? err\r\n");
                return -1;
		       }	 
	       }
	    }
	    else if(strstr((char *)pat_cmd,"="))
	    {
	         g_at_cmd.element=ELEM_NAME|ELEM_EQUAL_SIGN|ELEM_PARAMETER;
	         if(get_extend_at_cmd_name(pat_cmd,cmd_len,'=',g_at_cmd.name))
	         {
	            printf("= err\r\n");
                return -1;
             }
	         if(get_extend_at_cmd_parameters(pat_cmd,cmd_len,&(g_at_cmd.parameter_num)))
	         {
	            printf("Parameter err\r\n");
                return -1;
             }
	     }
	    else
	    {
             g_at_cmd.element=ELEM_NAME;
	         if(get_extend_at_cmd_name(pat_cmd,cmd_len,'\r',g_at_cmd.name))
	         {
	            printf("PART_NAME err\r\n");
                return -1;
	         }
	    }  
   }
   else //单字符名称类指令解析
   {

   }
      
   return 0;
}

根据得到的AtCmd_t结构体结果,进行命令分发处理,思路同上章命令行处理方式类似,

typedef int (*at_cmd_func)(const AtCmd_t *pCmd);

typedef struct 
{
   const char * pAtCmd;
   at_cmd_func  pCmdFunc;
   const char * pCmdInfo;
}AtCmdMap_t;

static int at_cmd_s_cmd_Handle(const AtCmd_t *pCmd);
static int at_cmd_atcmd_Handle(const AtCmd_t *pCmd);
static int at_cmd_test_parse_parameter_Handle(const AtCmd_t *pCmd);
static int at_cmd_softver_handle(const AtCmd_t *pCmd);
static int at_cmd_reboot_handle(const AtCmd_t *pCmd);
static int at_cmd_led_handle(const AtCmd_t *pCmd);
static int at_cmd_gps_handle(const AtCmd_t *pCmd);
static int at_cmd_battery_handle(const AtCmd_t *pCmd);
static int at_cmd_factory_handle(const AtCmd_t *pCmd);


static AtCmdMap_t at_cmd_func_table[]=
{ 	
    //基础指令
    {"&F"         ,at_cmd_atcmd_Handle  , "&F cmd"},  
	//S指令
	{"S"          ,at_cmd_s_cmd_Handle  , "S cmd"},    
    //扩展指令
    {"+test"      ,at_cmd_test_parse_parameter_Handle  , "reboot the system"},  
    {"+version"   ,at_cmd_softver_handle , "show version info"},   //at+version/at+version?            
    {"+reboot"    ,at_cmd_reboot_handle  ,"reboot the system\r\n"},//at+reboot	       
	{"+led"       ,at_cmd_led_handle     ,"set led example:led on/led off\r\n"},//at+led=on 
	{"+gps"       ,at_cmd_gps_handle     ,"gps open example:gps on/gps off\r\n"},
	{"+battery"   ,at_cmd_battery_handle ,"get battery voltage\r\n"},
	{"+factory"   ,at_cmd_factory_handle ,"factory test example:factory speaker\r\n"},
};

static int at_cmd_func_distributer(const AtCmd_t *pAtCmd)
{
    uint32_t i=0;
    uint32_t cmd_num=sizeof(at_cmd_func_table)/sizeof(at_cmd_func_table[0]);
	
    if(NULL==pAtCmd)
    {
         printf("Err:pAtCmd NULL\r\n");
         return -1;
    }
    
    for(i=0;i<cmd_num;i++)
    {
         if(0==strncasecmp(pAtCmd->name,at_cmd_func_table[i].pAtCmd,(strlen(pAtCmd->name)<strlen(at_cmd_func_table[i].pAtCmd)) ?(strlen(pAtCmd->name)): (strlen(at_cmd_func_table[i].pAtCmd))))
         {
              return at_cmd_func_table[i].pCmdFunc(pAtCmd);
	     }
    }
 
    return 0;
}

      具体到每条处理函数,AtCmd_t的类型的结果作为入参,可以通过其中的element成员来判断命令的格式是否正确,通过parameter_num成员来判断参数个数是否正确,p_para_line链表来访问参数数据。如: 

static int at_cmd_gps_handle(const AtCmd_t *pCmd)
{
    char *arg0=pCmd->p_para_line->data;//参数

	if(pCmd->element==ELEM_NAME|ELEM_EQUAL_SIGN|ELEM_PARAMETER)//判断命令格式是否满足要求
    {
        if(1==pCmd->parameter_num)//判断参数个数
        {
            if(strstr(arg0,"on")) //识别参数进行具体的处理 
		    {
		       //调用gps on接口 
			   //gps_turn_on();
			   printf("\r\ngps on\r\n"); 
			}
			else
			{
			   //调用gps off接口 
		       //gps_turn_off();
		       printf("\r\ngps off\r\n");
			}
		}
	  
    }
    else
    {
        printf("ERROR\r\n");   
    }	    
}

        以上就是构建AT命令处理机的整个实现过程,适用于基础类、S类、扩展类型指令,我们实际开发中用到的更多的是扩展类指令。AT命令处理的流程和思路和上章处理命令行基本是一致的,
在命令的解析方法上有差异,这里解析出了命令的结构元素用于匹配命令格式,通过单向链表的方式来存储多个参数,不受参数个数限制,非常方便。

到此结束,感觉各位同学的阅读、批评,指正!

结束

返回总目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值