liuqian 发表于 2019-1-31 13:56:23

请教一个能从串口通讯中检出特殊字符串的方法

本帖最后由 liuqian 于 2019-1-31 14:00 编辑

现在搞一个通讯协议,里面包含一些特殊的字符串,需要把这些字符串捡出来,比如
数据段1 : 1234567ERROR8474674
数据段2 : 37649944SYSTEMHALT4866289
这里面ERROR和SYSTEMHALT是需要捡出来的
数据流要把匹配字符串去掉,然后转发出去
数据段1变成12345678474674
数据段2变成376499444866289
假设正常的数据流中不会出现ERROR和SYSTEMHALT这样的字符串,但协议本身是未知的,就是说每收到一个字符,就要做匹配,如果能匹配,就先缓冲下来,不转发;不匹配的要马上转发出去。


const char ERROR[]="ERROR";
const char WARN[]="WARN";
const char HALT[]="SYSTEMHALT";

const struct {char * str; uint8_t len;} MATCHED_STR={
{ERROR,5},
{HALT,10},
{WARN,4}
};
// return:
//                0 - n : 和一个字符串完全匹配了
//                -1 :部分匹配了字符串,数据被缓冲
//                -2 : 发生了不匹配,需要处理缓冲区数据
int MatchByte()
{
        if(matchedLen<MATCHED_BUF_SIZE)
        {
                matchedBuf=getch();
        }
        int i;
        for(i=0;i<3;i++)
        {
                if(memcmp(matchedBuf,MATCHED_STR.str,matchedLen)==0) //matched first matchedLen bytes
                {
                        if(matchedLen==MATCHED_STR.len)
                        {// all matched
                                return i;
                        }
                        else
                        {// partly matched
                                return PARTLY_MATCHED;
                        }
                }
        }
        // none matched
        // 这里还没有想清楚怎么处理,卡住了
        return NONE_MATCHED;
}


调用

if(isDataRcvd())
{
        switch(MatchByte())
        {
                case 0:
                        error_handler();
                break;
                case 1:
                        halt_handler();
                break;
                case 2:
                        warn_handler();
                break;
                case NONE_MATCHED:
                        // 这里还没有想清楚怎么处理,卡住了
                break;
                case PARTLY_MATCHED:
                        // do nothing
                break;
        }
}

AWEN2000 发表于 2019-1-31 14:00:58

不能先缓存下来再查找?

lcw_swust 发表于 2019-1-31 14:14:14

看看库函数 strstr 行不行

JnzGoto 发表于 2019-1-31 14:24:17

你这个字符流又没有停止位,又没有传输间隔的说明,但是我觉得自己写函数处理吧,

AWEN2000 发表于 2019-1-31 14:44:59

本帖最后由 AWEN2000 于 2019-1-31 14:52 编辑


RecData 表示当前接收到的1个字节数据
const char ERROR[]="ERROR";
const char WARN[]="WARN";
const char HALT[]="SYSTEMHALT";

static INT8U State;
static INT8U Index;
static INT8U Buf;
      switch(State)
      {
          case 0:
                  if(RecData=='E')
                     { State=1;
                     Index=1;
                     Buf=RecData;
                     break;
                        }
                  if(RecData=='S')
                     { State=2;
                     Index=1
                      Buf=RecData;
                     break;
                        }
                     if(RecData=='W')
                     { State=3;
                     Index=1
                     Buf=RecData;
                     break;
                     }
                  SendData(&RecData,1);//把数据发送出去 1个字节
                  break;
      case 1://收到'E'
                  if((RecData!=ERROR)
                  {
                      SendData(Buf,Index);//把缓存的数据发送出去
                     State=0;//回到初始状态
                     break;
                     }
                   Buf=RecData;
                  Index++;
               if(Index==strlen(ERROR))
                   State=0;//完整接收了也回到初始状态
               break;
       case 2://收到'S'
                  if((RecData!=HALT)
                  {
                        SendData(Buf,Index);//把缓存的数据发送出去
                     State=0;//回到初始状态
                     break;
                     }
                  Buf=RecData;
                  Index++;
               if(Index==strlen(HALT))
                   State=0;//完整接收了也回到初始状态
               break;
         case 3://收到'W'
                  if((RecData!=WARN)
                  {
                     SendData(Buf,Index);//把缓存的数据发送出去
                     State=0;//回到初始状态
                     break;
                     }
                   Buf=RecData;
                  Index++;
               if(Index==strlen(WARN))
                   State=0;//完整接收了也回到初始状态
               break;
          default:
                      State=0;
                     break;
         }

shiva_shiva 发表于 2019-1-31 17:31:41

状态机。。。

lcw_swust 发表于 2019-2-15 12:00:21

AWEN2000 发表于 2019-1-31 14:44
RecData 表示当前接收到的1个字节数据
const char ERROR[]="ERROR";
const char WARN[]="WARN";


虽然我也用状态机,但这里似乎不太好处理。
比如收到的是“EERROR”,则在状态1时认为不匹配,就会漏掉“ERROR”。
也许需要做一个FIFO,缓冲最后5个字节,每收到一个字节都让这个FIFO与“ERROR”去比较,只是比较占CPU。

yerrmin 发表于 2019-2-15 12:48:54

每收到一个字符处理一次,用状态机
假设里面包含EERROR,第一个E进入下一状态,第二个E不匹配ERROR,是PASS第一个E,而不是第二个E也PASS,第二个E要重新过一遍状态机

yerrmin 发表于 2019-2-15 12:55:04

当然像这种匹配很多种字符串的话,用状态机比较费劲,编程方便的话就是FIFO,每收到一个字节比一次,比较其实不费时间,把FIFO效率提高就行

AWEN2000 发表于 2019-2-15 13:06:01

RecData 表示当前接收到的1个字节数据 const char ERROR[]="ERROR"; const char WARN[]="WARN"; const char HALT[]="SYSTEMHALT";static INT8U State; static INT8U Index; static INT8U Buf; INT8U Flag; Start:       Flag=0;       switch(State)         {           case 0:                     if(RecData=='E')                      { State=1;                        Index=1;                        Buf=RecData;                        break;                         }                     if(RecData=='S')                      { State=2;                        Index=1                       Buf=RecData;                        break;                         }                      if(RecData=='W')                      { State=3;                        Index=1                        Buf=RecData;                        break;                        }                     SendData(&RecData,1);//把数据发送出去 1个字节                     break;        case 1:  //收到'E'                   if((RecData!=ERROR)                     {                       SendData(Buf,Index);//把缓存的数据发送出去                        State=0;//回到初始状态                        Flag=1;再次检测"E"                        break;                        }                    Buf=RecData;                   Index++;                  if(Index==strlen(ERROR))                    State=0;  //完整接收了也回到初始状态                  break;        case 2:  //收到'S'                   if((RecData!=HALT)                     {                         SendData(Buf,Index);//把缓存的数据发送出去                        State=0;//回到初始状态                     Flag=1;                        break;                        }                   Buf=RecData;                   Index++;                  if(Index==strlen(HALT))                    State=0;  //完整接收了也回到初始状态                  break;          case 3:  //收到'W'                   if((RecData!=WARN)                     {                        SendData(Buf,Index);//把缓存的数据发送出去                        State=0;//回到初始状态                        Flag=1;                        break;                        }                    Buf=RecData;                   Index++;                  if(Index==strlen(WARN))                    State=0;  //完整接收了也回到初始状态                  break;           default:                       State=0;                      break;          } if(Flag==1)   goto Start;

AWEN2000 发表于 2019-2-15 13:08:04

RecData 表示当前接收到的1个字节数据
const char ERROR[]="ERROR";
const char WARN[]="WARN";
const char HALT[]="SYSTEMHALT";

static INT8U State;
static INT8U Index;
static INT8U Buf;
INT8U Flag;
Start:
      Flag=0;
      switch(State)
        {
          case 0:
                    if(RecData=='E')
                     { State=1;
                       Index=1;
                       Buf=RecData;
                       break;
                        }
                    if(RecData=='S')
                     { State=2;
                       Index=1
                      Buf=RecData;
                       break;
                        }
                     if(RecData=='W')
                     { State=3;
                       Index=1
                       Buf=RecData;
                       break;
                       }
                    SendData(&RecData,1);//把数据发送出去 1个字节
                    break;
        case 1:  //收到'E'
                  if((RecData!=ERROR)
                    {
                      SendData(Buf,Index);//把缓存的数据发送出去
                       State=0;//回到初始状态
                     Flag=1;再次检测"E"
                       break;
                       }
                   Buf=RecData;
                  Index++;
                 if(Index==strlen(ERROR))
                   State=0;  //完整接收了也回到初始状态
                 break;
       case 2:  //收到'S'
                  if((RecData!=HALT)
                    {
                        SendData(Buf,Index);//把缓存的数据发送出去
                       State=0;//回到初始状态
                      Flag=1;
                       break;
                       }
                  Buf=RecData;
                  Index++;
                 if(Index==strlen(HALT))
                   State=0;  //完整接收了也回到初始状态
                 break;
         case 3:  //收到'W'
                  if((RecData!=WARN)
                    {
                       SendData(Buf,Index);//把缓存的数据发送出去
                       State=0;//回到初始状态
                     Flag=1;
                       break;
                       }
                   Buf=RecData;
                  Index++;
                 if(Index==strlen(WARN))
                   State=0;  //完整接收了也回到初始状态
                 break;
          default:
                      State=0;
                     break;
         }
if(Flag==1)
    goto Start;




状态1里面若出现不匹配,把以前缓存的数据发出去。同时对当前的字节再次检测特征字头

canspider 发表于 2019-2-15 13:42:43

yerrmin 发表于 2019-2-15 12:48
每收到一个字符处理一次,用状态机
假设里面包含EERROR,第一个E进入下一状态,第二个E不匹配ERROR,是PASS ...

用KMP算法,不需要再过状态机

canspider 发表于 2019-2-15 13:45:03

如果是我做的话,我会用词法分析工具自动成生成状态机

串口数据不停止往状态机里写,状态机自动吐数据

lcw_swust 发表于 2019-2-16 10:06:32

本帖最后由 lcw_swust 于 2019-2-16 10:12 编辑

AWEN2000 发表于 2019-2-15 13:08
RecData 表示当前接收到的1个字节数据
const char ERROR[]="ERROR";
const char WARN[]="WARN";


这种方法的确可以适用大多数情况,但在某些特殊情况下仍会漏掉。
比如在“SYSYST”中查找“SYST”。
只是这种情况相当少,一般情况够用了。

12楼说的KMP算法似乎不错,相比挨个位置匹配,效率提高了,但是程序较复杂。

bangbangji 发表于 2019-2-16 11:41:33

lcw_swust 发表于 2019-2-16 10:06
这种方法的确可以适用大多数情况,但在某些特殊情况下仍会漏掉。
比如在“SYSYST”中查找“SYST”。
只是 ...

kmp算法需要先把数都收下来才能查的,似乎不能直接用?除非收一部分先查这一部分。

lcw_swust 发表于 2019-2-16 11:51:02

bangbangji 发表于 2019-2-16 11:41
kmp算法需要先把数都收下来才能查的,似乎不能直接用?除非收一部分先查这一部分。 ...

是啊,要缓冲一部分。

anjiyifan 发表于 2019-2-16 11:59:37

这需求和GPRS模块AT命令解析类似。

liuqian 发表于 2019-2-17 16:14:14

现在用FIFO缓冲,然后再加一个超时,每收一个字符就比较。效率比较低,但功能实现了。有空把程序整一下再贴出来。

canspider 发表于 2019-2-17 21:21:25

bangbangji 发表于 2019-2-16 11:41
kmp算法需要先把数都收下来才能查的,似乎不能直接用?除非收一部分先查这一部分。 ...

kmp算法是先对特征字符串进行计算
计算出匹配错误时,下一次需要从哪里重新开始匹配
接受还是一字节一字节的处理,不需要全部接收再处理

canspider 发表于 2019-2-17 21:26:20

lcw_swust 发表于 2019-2-16 10:06
这种方法的确可以适用大多数情况,但在某些特殊情况下仍会漏掉。
比如在“SYSYST”中查找“SYST”。
只是 ...

正则表达式/词法分析的处理方式是效率最高的,也是最灵活的
对于sysyst中匹配syst的情况,很容易处理
代码也是最复杂的,不过一般这样的代码都不用手写
有工具生成

wajlh 发表于 2019-2-17 23:38:37

anjiyifan 发表于 2019-2-16 11:59
这需求和GPRS模块AT命令解析类似。

AT命令解析的场景,允许先把收到的数据全部缓存起来,这样就容易多了。而且AT指令要求的响应不高,逐个匹配都可以。

ronic 发表于 2019-2-18 00:06:15

不错的流串匹配算法,也许会用到,谢谢
页: [1]
查看完整版本: 请教一个能从串口通讯中检出特殊字符串的方法