自定义串口协议

2023-05-16

文章目录

  • 前言
  • 一、有限状态机
    • 有限状态机代码
    • 接收数据缓冲
      • 缓冲要求
      • 循环队列
  • 一、循环队列
    • 代码实现
      • 循环队列头文件:
      • 源文件:
  • 二、有限状态机与解码
    • 有限状态机核心实现代码
    • 有限状态机与解码
    • 头文件
    • 源文件
  • 三、使用小例子
  • 总结


前言

此篇将结合有限状态机循环队列,构建一个稳定可靠的自定义串口协议,如果代码有点难懂,可以直接移植使用,看使用demo就好啦,我都封装好了。

Gitee链接在这里:
基于串口的有限状态机

简单提一下实现的主要功能:

一·:命令模式,根据输入字符与缓冲区的内容进行比对,返回真假值。

二·:调参模式,根据输入字符与缓冲区的内容进行比对,返回传入数字的值。这个用来调试参数还是比较好用的,比如调试PID就可以直接输入PID_P=0.111之类的命令,就返回输入参数的1000倍,用来在线调参。


一、有限状态机

有限状态机,这里仅仅使用简单的4个状态,状态之间的转换可以根据下图。根据接收到的不同的字符类型进行状态之间的转换。

在此状态机中的,源状态是缓存区完成一次接受的空状态;触发事件便是接收到一个字符(所以一般放在中断里面进行判断);监护条件便是接受到的字符的类型,动作便是切换条件与存入缓冲。

下图中:箭头表示状态之间的切换,箭头旁边的字母便是切换状态的条件。图中IDEL表示准备好接收数据;HEAD接收到头状态;DATA接收数据状态(可以一直接收数据);TAIL接收到尾数据,END表示,接收数据有误,清楚错误数据。

在设计的自定义串口协议中:仅仅只有帧头和帧尾,帧头和帧尾都可以自己通过初始化函数进行设计。
在这里插入图片描述

有限状态机代码

根据上图可以设计如下代码,使用枚举设计4种状态。

/*协议有限状态机枚举*/
typedef enum
{
    STATUS_IDEL=(uint8_t) 0,
    STATUS_HEAD,
    STATUS_DATA,
    STATUS_END,
}COM_STATUS;

接收数据缓冲

为了更高效的处理数据,使用缓冲区,将接收到的正确的帧存入缓冲区,缓冲区可以存入n个有效帧。

缓冲要求

设计的缓冲区应该是先进先出的形式,也就是优先处理旧的指令。使用队列作为缓冲区是符合要求的,队列就像是我们排队一样,先排的人先处理,后加入的人后处理。

循环队列

在有限的单片机资源中,我们希望队列有人加入和出的时候,尽可能的少操作,也就是我们排队中不希望前面走了一个人,后面的人要跟上一个位置。这样会移动队列,浪费大家精力(CPU资源)。因此选择使用循环队列,将队列构成一个循环,利用一个游标,游标表示当前排到的个人。前面的人走了,游标便移动到下一个,后面的人不需要移动。这样移动游标就能知道下一个人是谁,不需要移动整个队列,减少了CPU的资源消耗。

一、循环队列

循环队列比较麻烦,想要了解具体的实现原理,请自己百度或者找教材来学习啦!这里就贴上基于面向对象的方式实现的循环队列,想要学习使用C语言实现面向对象,可以看我往期文章–>C语言实现面向对象

如果对void *指针还不是怎么了解的同学,可以看我往前文章–>C语言值Void *指针

代码实现

代码都挺好理解的(前提理解好队列和C语言实现面向对象的方式),就不写过多注释啦~

循环队列头文件:

#ifndef _QUE_OOP_H
#define _QUE_OOP_H

#define FALSE 0
#define TRUE 1

typedef unsigned char cbool;
typedef unsigned char uint8_t;

#define QUE_MAX_LEN 100

typedef char QUEUE_TYPE;
struct Que_vtable;

/*创建循环队列类*/

/*循环队列实际上只有K-1个空间能用*/
/*队列为空:头=尾 */
/*队列满:头=尾+1%最大长度,留出一个空位作为满的条件,不然头=尾的情况和空的情况重复*/

/*循环队列结构体*/
typedef struct Cir_queue
{
    struct Que_vtable *c_vptr;

    QUEUE_TYPE  Queue_Buffer[QUE_MAX_LEN];//缓冲数组
    int head,tail,max_len,lenth;
}Cir_queue;

/*队列虚表*/
struct Que_vtable
{
    // void (*create_queue)(void * Me);
    void (*delete_queue)(void * const Me);

    cbool (*empty)(void const * const Me);
    cbool (*full)(void const * const Me);

    cbool (*pop)(void * const Me,QUEUE_TYPE *Get); 
    cbool (*push)(void * const Me,QUEUE_TYPE value);

    cbool (*head_push)(void * const Me,QUEUE_TYPE value);
    cbool (*tail_pop)(void * const Me,QUEUE_TYPE *Get); 

    cbool (*back)(void const * const Me,QUEUE_TYPE *Get);
    cbool (*front)(void const * const Me,QUEUE_TYPE *Get);  

    int   (*lenth)(void const * const Me);

}Que_vtable;

/*多态类*/
// void Qcreate_queue(void * const Me);
void Qdreate_queue(void * const Me);
cbool Qempty(void const * const Me);
cbool Qfull(void const * const Me);
cbool Qtail_pop(void * const Me,QUEUE_TYPE *Get);
cbool Qhead_push(void * const Me,QUEUE_TYPE value);
cbool Qpop(void * const Me,QUEUE_TYPE *Get);
cbool Qpush(void * const Me,QUEUE_TYPE value);
cbool Qback(void const * const Me,QUEUE_TYPE *Get);
cbool Qfront(void const * const Me,QUEUE_TYPE *Get);
int Q_lenth(void const * const Me);

/*循环队列*/
void Cir__Qcreate_queue(Cir_queue * const Me);
void Cir__Qdreate_queue(Cir_queue * const Me);
cbool Cir__Qempty(Cir_queue const * const Me);
cbool Cir__Qfull(Cir_queue const * const Me);
cbool Cir_Qtail_pop(Cir_queue * const Me,QUEUE_TYPE *Get);
cbool Cir_Qhead_push(Cir_queue * const Me,QUEUE_TYPE value);
cbool Cir__Qpush(Cir_queue * const Me,QUEUE_TYPE value);
cbool Cir__Qpop(Cir_queue * const Me,QUEUE_TYPE *Get);
cbool Cir__Qback(Cir_queue const * const Me,QUEUE_TYPE *Get);
cbool Cir__Qfront(Cir_queue const * const Me,QUEUE_TYPE *Get);
int Cir__Q_lenth(Cir_queue const * const Me);

源文件:

void * my_memset(void *source,int dest,int n)
{
    char *c_s=(char *)source;
    while(n--) *c_s++=dest;
    return source;
}

/*多态实现*/
inline void Qdreate_queue(void * const Me)
{
    Cir_queue const * const _Me=(Cir_queue * const)Me;
    
    _Me->c_vptr->delete_queue(Me);
}

inline cbool Qempty(void const * const Me)
{
    Cir_queue const * const _Me=(Cir_queue * const)Me;
    
    return _Me->c_vptr->empty(Me);
}


inline cbool Qfull(void const * const Me)
{
    Cir_queue const * const _Me=(Cir_queue const * const)Me;
    
    return _Me->c_vptr->full(Me);
}

inline cbool Qpop(void * const Me,QUEUE_TYPE *Get)
{
    Cir_queue const * const _Me=(Cir_queue * const)Me;
    
    return _Me->c_vptr->pop(Me,Get);
}

inline cbool Qpush(void * const Me,QUEUE_TYPE value)
{
    Cir_queue const * const _Me=(Cir_queue * const)Me;
    
    return _Me->c_vptr->push(Me,value);    
}

inline cbool Qtail_pop(void * const Me,QUEUE_TYPE *Get)
{
    Cir_queue const * const _Me=(Cir_queue * const)Me;
    
    return _Me->c_vptr->pop(Me,Get);
}

inline cbool Qhead_push(void * const Me,QUEUE_TYPE value)
{
    Cir_queue const * const _Me=(Cir_queue * const)Me;
    
    return _Me->c_vptr->push(Me,value); 
}

inline cbool Qback(void const * const Me,QUEUE_TYPE *Get)
{
    Cir_queue const * const _Me=(Cir_queue * const)Me;
    
    return _Me->c_vptr->back(Me,Get);    
}

inline cbool Qfront(void const * const Me,QUEUE_TYPE *Get)
{
    Cir_queue const * const _Me=(Cir_queue * const)Me;
    
    return _Me->c_vptr->front(Me,Get);    
}

inline int Q_lenth(void const * const Me)
{
    Cir_queue const * const _Me=(Cir_queue * const)Me;
    
    return _Me->c_vptr->lenth(Me);      
}

void Cir__Qcreate_queue(Cir_queue * const Me)
{
    /*清零*/
    my_memset(Me,0,sizeof(Cir_queue));
    /*函数表绑定*/
    static struct Que_vtable  table;
    // table.create_queue=(void (*)(void *))(void(*)(Cir_queue *))Cir__Qcreate_queue;
    table.delete_queue=(void (*)(void *))(void(*)(Cir_queue *))Cir__Qdreate_queue;
    table.empty=(cbool (*)(void const *const ))(cbool(*)(Cir_queue  const* const))Cir__Qempty;
    table.full=(cbool (*)(void const * const ))(cbool(*)(Cir_queue const *const))Cir__Qfull;
    table.pop=(cbool (*)(void *const ,QUEUE_TYPE *Get))(cbool(*)(Cir_queue *const,QUEUE_TYPE *Get))Cir__Qpop;
    table.push=(cbool (*)(void *const,QUEUE_TYPE value))(cbool(*)(Cir_queue *const,QUEUE_TYPE value))Cir__Qpush;
    table.tail_pop=(cbool (*)(void *const,QUEUE_TYPE *Get))(cbool(*)(Cir_queue *const,QUEUE_TYPE *Get))Cir_Qtail_pop;
    table.head_push=(cbool (*)(void *const,QUEUE_TYPE value))(cbool(*)(Cir_queue *const,QUEUE_TYPE value))Cir_Qhead_push;
    table.back=(cbool (*)(void  const* const,QUEUE_TYPE *Get))(cbool(*)(Cir_queue  const*const ,QUEUE_TYPE *Get))Cir__Qback;
    table.front=(cbool (*)(void  const* const,QUEUE_TYPE *Get))(cbool(*)(Cir_queue  const*const ,QUEUE_TYPE *Get))Cir__Qfront;
    table.lenth=(int (*)(void  const* const))(int (*)(Cir_queue  const*const))Cir__Q_lenth;
    Me->c_vptr=&table;

    Me->max_len=QUE_MAX_LEN;
}

void Cir__Qdreate_queue(Cir_queue * const Me)
{
    return ;
}
cbool Cir__Qempty(Cir_queue const * const Me)
{
    if(Me->head==Me->tail) return TRUE;
    else return FALSE;
}
cbool Cir__Qfull(Cir_queue const * const Me)
{
    if(Me->head==(Me->tail+1)%QUE_MAX_LEN) return TRUE;
    else return FALSE;
}

cbool Cir__Qpop(Cir_queue * const Me,QUEUE_TYPE *Get)
{
    if(Cir__Qempty(Me)) return FALSE;
       
    else
    {
        *Get=Me->Queue_Buffer[Me->head];
        Me->head=(Me->head+1)%QUE_MAX_LEN;
        Me->lenth--;
        return TRUE;
    }     

}

cbool Cir__Qpush(Cir_queue * const Me,QUEUE_TYPE value)
{
    if(Cir__Qfull(Me)) return FALSE;
       
    else
    {
        Me->Queue_Buffer[Me->tail]=value;
        Me->tail=(Me->tail+1)%QUE_MAX_LEN;
        Me->lenth++;
        return TRUE;
    }    
}

cbool Cir_Qtail_pop(Cir_queue * const Me,QUEUE_TYPE *Get)
{
    if(Cir__Qempty(Me)) return FALSE;
       
    else
    {
        Me->tail=((Me->tail-1+QUE_MAX_LEN)%QUE_MAX_LEN);
        *Get=Me->Queue_Buffer[(Me->tail)];
        Me->lenth--;
        return TRUE;
    }        
}

cbool Cir_Qhead_push(Cir_queue * const Me,QUEUE_TYPE value)
{
    if(Cir__Qfull(Me)) return FALSE;
       
    else
    {
        Me->head=((Me->head-1+QUE_MAX_LEN)%QUE_MAX_LEN);
        Me->Queue_Buffer[Me->head]=value;
        Me->lenth++;
        return TRUE;
    }      
}

cbool Cir__Qback(Cir_queue const * const Me,QUEUE_TYPE *Get)
{
    if(Cir__Qempty(Me)) return FALSE;
    else
    {
        *Get=Me->Queue_Buffer[(Me->tail-1)%QUE_MAX_LEN];
        return TRUE;
    }

}

cbool Cir__Qfront(Cir_queue const * const Me,QUEUE_TYPE *Get)
{
    if(Cir__Qempty(Me)) return FALSE;
    else
    {
        *Get=Me->Queue_Buffer[Me->head%QUE_MAX_LEN];
        return TRUE;
    }
}

int Cir__Q_lenth(Cir_queue const * const Me)
{
    return Me->lenth;
}

二、有限状态机与解码

有限状态机核心实现代码

有限状态机,主要思想是下面的是实现代码,也就是上面提到的那图的的代码实现,配合图片是用更佳。

/*有限状态机读入缓存函数*/
void Com_rxUsart_data(Cir_queue * const QMe,Usart_Trm *const Me,uint8_t bydata)
{

    switch (Me->usart_status)
    {
        case STATUS_IDEL:
        if(bydata==Me->head)
        {
            Me->usart_status=STATUS_HEAD;
            Me->last_pos++;
        }
        
        else Me->usart_status=STATUS_END;
        break;
        
        case STATUS_HEAD:    
        if(bydata!=Me->teal) 
        {
            Me->usart_status=STATUS_DATA;
            Me->last_pos++;
        }

        else Me->usart_status=STATUS_END;
        break;
        
        case STATUS_DATA:
        if(bydata!=Me->teal&&!QMe->c_vptr->full(QMe)) 
        {
            Me->usart_status=STATUS_DATA;
            Me->last_pos++;
        }
        else if(bydata==Me->teal) 
        {
            Me->last_pos=0;/*成功接收到头和尾,没有错误数据*/
            Me->Uartx_frame++; /*队列中有效帧+1*/
            Me->usart_status=STATUS_IDEL;
        }

        else Me->usart_status=STATUS_END;
        break;

        case STATUS_END: break;

        default:Me->usart_status=STATUS_END;break;
    }

    if(Me->usart_status==STATUS_END)
    {
        QUEUE_TYPE temp;
        /*把出错数据取出,从尾取出*/
        while(Me->last_pos) 
        {
            QMe->c_vptr->tail_pop(QMe,&temp);
            Me->last_pos--;
        }
        Me->usart_status=STATUS_IDEL;
    }

    else
    {
        QMe->c_vptr->push(QMe,bydata);/*数据入队列*/
    }

}

有限状态机与解码

解码主要是个人便于开发的两个简单的小功能,核心实现就是比对缓冲区的内容,进行有效的判断和处理。

处理过程如下:

  1. 串口接收有效数据进入队列缓冲区
  2. 主函数调用Frame_deal对传入的指令进行比对,如果缓冲区内没有相应的指令,则取出最找传入的一帧数据。有相应指令则进行处理。

头文件

头文件包含循环队列的头文件

#ifndef __STATUS__U
#define __STATUS__U
/*协议有限状态机枚举*/
typedef enum
{
    STATUS_IDEL=(uint8_t) 0,
    STATUS_HEAD,
    STATUS_DATA,
    STATUS_END,
}COM_STATUS;

#define COMMAND_LIST_MAX 10 /*命令列表长度*/
#define COMMAND_LEN_MAX 20  /*命令最大长度*/

struct Usart_vtable;
/*协议有限状态机结构体*/
typedef struct Usart_Trm
{
    /*有效帧格式: (data),不支持中文输入*/
	uint8_t  Uartx_frame;   /*有效数据帧数*/
	uint8_t head;           /*头帧标志*/
	uint8_t teal;            /*尾帧标志*/
    COM_STATUS usart_status;  /*状态机*/
    int last_pos;           /*错误数据计数*/
    
    struct Usart_vtable *c_vptr;/*函数表,提供接口接口使用*/
}Usart_Trm;

struct Usart_vtable
{
    // void (*create_ComUsart)(Usart_Trm *Me,uint8_t set_h,uint8_t set_t);
    void (*rx_buff)(Cir_queue * const QMe,Usart_Trm *const Me,uint8_t bydata);

    int (*fram_num)(Usart_Trm *Me);
    long (*fram_deal)(Cir_queue *QMe,Usart_Trm *Me,const char *sdata);

}Usart_vtable;

void Create_ComUsart(Usart_Trm *Me,uint8_t set_h,uint8_t set_t);
void Com_rxUsart_data(Cir_queue * const QMe,Usart_Trm *const Me,uint8_t bydata);

int Get_fram_num(Usart_Trm *Me);
long Deal_com(Cir_queue *QMe,Usart_Trm *Me,const char *sdata);

void Com_tailpush(Cir_queue *QMe,Usart_Trm *Me,char const *get_str);

#endif

源文件

/*有限状态机初始化*/
void Create_ComUsart(Usart_Trm *Me,uint8_t set_h,uint8_t set_t)
{   
    static struct Usart_vtable vtable;

    my_memset(Me,0,sizeof(Usart_Trm));

    vtable.rx_buff=&Com_rxUsart_data;
    vtable.fram_deal=&Deal_com;
    vtable.fram_num=&Get_fram_num;   
    Me->c_vptr=&vtable;

    Me->head=set_h;
    Me->teal=set_t;
    Me->usart_status=STATUS_IDEL;
}

/*有限状态机读入缓存函数*/
void Com_rxUsart_data(Cir_queue * const QMe,Usart_Trm *const Me,uint8_t bydata)
{

    switch (Me->usart_status)
    {
        case STATUS_IDEL:
        if(bydata==Me->head)
        {
            Me->usart_status=STATUS_HEAD;
            Me->last_pos++;
        }
        
        else Me->usart_status=STATUS_END;
        break;
        
        case STATUS_HEAD:    
        if(bydata!=Me->teal) 
        {
            Me->usart_status=STATUS_DATA;
            Me->last_pos++;
        }

        else Me->usart_status=STATUS_END;
        break;
        
        case STATUS_DATA:
        if(bydata!=Me->teal&&!QMe->c_vptr->full(QMe)) 
        {
            Me->usart_status=STATUS_DATA;
            Me->last_pos++;
        }
        else if(bydata==Me->teal) 
        {
            Me->last_pos=0;/*成功接收到头和尾,没有错误数据*/
            Me->Uartx_frame++; /*队列中有效帧+1*/
            Me->usart_status=STATUS_IDEL;
        }

        else Me->usart_status=STATUS_END;
        break;

        case STATUS_END: break;

        default:Me->usart_status=STATUS_END;break;
    }

    if(Me->usart_status==STATUS_END)
    {
        QUEUE_TYPE temp;
        /*把出错数据取出,从尾取出*/
        while(Me->last_pos) 
        {
            QMe->c_vptr->tail_pop(QMe,&temp);
            Me->last_pos--;
        }
        Me->usart_status=STATUS_IDEL;
    }

    else
    {
        QMe->c_vptr->push(QMe,bydata);/*数据入队列*/
    }

}

/*实现两种命令方式,自定义根据有没有'='号自行判断命令类型*/

/*判断命令类型*/
static int Com_type(const char * const str)
{
    char *buf_ptr=(char *)str;
	int count = 0;
    
    /*内容为空*/
    if(*(buf_ptr+count)=='\0') return 0;	

	while (*(buf_ptr+count)!='='&&*(buf_ptr+count)!='\0')//'='是判断的位置,也是命令停止标志
	{
		count++;
	}

    if(*(buf_ptr+count)=='=') 
    {
       return count;
    }

	else 
    {
        return 0;
    }
}

/*读取取出1帧的内容,保存帧头和帧尾,返回读取到的内容*/
static char * Get_fram_data(Cir_queue *QMe,Usart_Trm *Me)
{
    static char buff[50],get_char;
    
    int i=0;
    my_memset(buff,0,sizeof(buff));

    while(Me->Uartx_frame!=0&&QMe->c_vptr->pop(QMe,&get_char)) 
    {
        if(get_char==')') 
        {
            Me->Uartx_frame--;
            break;
        }
        buff[i++]=get_char;
    }
    buff[i++]=')';
    return buff;
}

/*将命令放在尾部*/
void Com_tailpush(Cir_queue *QMe,Usart_Trm *Me,char const *get_str)
{

    Me->usart_status==STATUS_END;
    /*解决传入命令不完成的问题,比如原来数据:(111,还未接收完成(标记为错误,重新接收),插入就会变成(111(112)*/
    if(Me->last_pos!=0) 
    {
        QUEUE_TYPE temp;
        for(int i=Me->last_pos;i>0;i--) QMe->c_vptr->tail_pop(QMe,&temp);
        Me->usart_status=STATUS_IDEL;
        Me->last_pos=0;
    }
    /*把当前数据的帧头和帧尾放进列表尾部*/
    while(*get_str!='\0')
    {
        Me->c_vptr->rx_buff(QMe,Me,*get_str);/*存入数据进入缓冲区*/
        get_str++;
    }

    Me->c_vptr->rx_buff(QMe,Me,*get_str);/*将帧尾存入*/
}

/*返回缓存区的帧数*/
int Get_fram_num(Usart_Trm *Me)
{
    return Me->Uartx_frame;
}

/*返回0则为命令假,返回其他则为真,数据默认扩大1000倍,返回1则为命令模式*/
long Deal_com(Cir_queue *QMe,Usart_Trm *Me,const char *sdata)
{
	char com_buf[COMMAND_LEN_MAX]={0};//临时保存内容数据
    int eque_pos=0,back_data;

    float temp=0;
	char *get_string;

    int i=0;
    /*遍历队列中所有命令,找到符合命令的*/
    while (i<=Me->c_vptr->fram_num(Me))
    {
        i++;
        get_string=Get_fram_data(QMe,Me);    /*获取帧内容*/
        #ifdef CIRDEBUG
        printf("Get string is %s \n",get_string);
        #endif 
        /*遍历一遍找到了,则返回*/
        if(strncmp((const char *)get_string+1,sdata,strlen(sdata))==0)  break;
    
        Com_tailpush(QMe,Me,get_string);
    }
    
    /*遍历完都没有找到,丢弃当前帧,当前为最先插入的帧*/
    if(i==Me->c_vptr->fram_num(Me)+1) return 0;

    eque_pos=Com_type(++get_string);

    /*返回输入命令中的数字*/
	if(eque_pos!=0&&*get_string!='\0')
	{	
        /*把尾帧去掉*/
        for(int i=0;i<strlen(get_string)-(eque_pos+1);i++)
        {
             com_buf[i]=*(get_string+eque_pos+i+1);/*把等于号后面的全部数字保存*/
        }
		temp=atof(com_buf);/*字符串转换成为浮点型数字*/
        back_data=temp*1000;/*扩大100倍*/
        return back_data;
	}   
    /*符合命令,不反回参数*/
	else if(eque_pos==0&&*get_string!='\0')   return 1;
    /*其他情况*/
    else   return 0;
}

三、使用小例子

小例子代码如下

int main()
{
    long x;
    Usart_Trm usart_trm;
    Cir_queue que;
    int cout=0;

    /*假设实际接收到的数据*/
    char AS[20]="(PID_P=1.11)";
    char BS[20]="(PID_I=2.22)";
    char CS[20]="(PID_D=3.33)";

    char COM[20]="(SET)";

    Create_ComUsart(&usart_trm,'(',')');
    Cir__Qcreate_queue(&que);


    /*模拟串口存入数据*/
    for(int j=0;j<5;j++)
    {
        for(int i=0;i<strlen(AS);i++)
        {
            /*逐个数据存入*/
            if(j==0) usart_trm.c_vptr->rx_buff(&que,&usart_trm,AS[i]);/*PID_P*/
            if(j==1) usart_trm.c_vptr->rx_buff(&que,&usart_trm,BS[i]);/*PID_I*/
            if(j==2) usart_trm.c_vptr->rx_buff(&que,&usart_trm,CS[i]);/*PID_D*/
            if(j==3) usart_trm.c_vptr->rx_buff(&que,&usart_trm,AS[i]);/*PID_P*/
            if(j==4) usart_trm.c_vptr->rx_buff(&que,&usart_trm,COM[i]);/*SET*/
        }
    }

    if(Get_fram_num(&usart_trm))
    {
        /*调参模式*/
        /*传入指令*/
        x=usart_trm.c_vptr->fram_deal(&que,&usart_trm,"PID_D=");
        printf("x=%d\n",x);                                     
        x=usart_trm.c_vptr->fram_deal(&que,&usart_trm,"PID_D=");
        printf("x=%d\n",x);                                     
        x=0;
        x=usart_trm.c_vptr->fram_deal(&que,&usart_trm,"PID_I=");
        printf("x=%d\n",x);                                     
        x=0;    
        x=usart_trm.c_vptr->fram_deal(&que,&usart_trm,"PID_P=");
        printf("x=%d\n",x);                                     
        x=0;
        /*命令模式*/
        x=usart_trm.c_vptr->fram_deal(&que,&usart_trm,"SET");
        printf("x=%d\n",x);                                     
                     
    }
    system("pause");
}


运行结果:

x=3330
x=0
x=2220
x=1110
x=1

总结

使用起来还是比较方便的,也能够存入相关数据,可以通过函数指针进行访问。

Gitee链接在这里:
基于串口的有限状态机

之前我也写过一篇类似的文章,里面有介绍大概怎么在STM32移植,这篇是基于STM32的自定义串口协议。这个是半年前写的,代码有点惨不忍睹,看看怎么移植就好了。
参考文章:

基于串口设计的状态机

游戏开发状态机使用

C语言实现状态机

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

自定义串口协议 的相关文章

  • AStar寻路算法 (C#)

    一 介绍 A星算法其实并不是最短路径算法 xff0c 它找到的路径并不是最短的 xff0c 它的目标首先是能以最快的速度找到通往目的地的路 B星实际上是A星的优化 但是B星的缺点是不能向后查找 所以会有问题 还有一种D星的可以用来找最短路径
  • 深度相机介绍(TOF、RGB双目、结构光参数对比)

    一 深度相机的介绍 随着计算机视觉与人工智能技术的飞速发展 xff0c 采用深度相机进行场景三维重建 目标检测 环境感知等应用越来越广泛 xff0c 与传统的2D相机不同 xff0c 深度相机可以通过拍摄空间来获得景深信息 xff0c 从而
  • 网络编程——UDP

    目录 UDP的服务器端 UDP的echo客户端代码 UDP的echo服务器端代码 UDP的服务器端 先运行服务器端 xff0c 再运行客户端 服务端 xff1a 开发者 xff1a Virtuous 开发版本 xff1a 1 0 开发时间
  • HTTP 完全解析

    Http 详解 HTTP 的定义 HTTP即是 xff1a Hypertext Transfer Protocol xff0c 超文本传输协议 xff0c 种 络传输协议 xff0c 位于 TCP IP 协议族的最顶层 应 层 URL 说到
  • STM32 自定义串口协议

    STM32 自定义串口协议 1 串行通信1 1 原理与优缺点1 2 分类1 2 1 按通信方向1 2 2 按通信方式 1 3 异步串行引脚连接1 3 1 串口外设之间1 3 2 ARM与PC之间 1 4 字符帧格式1 5 串口通信过程1 6
  • 无人机——电机篇(一)

    文章目录 1 电机的定义 2 电机的分类 3 电机的参数 4 电机效率 5 电机的选择 1 电机的定义 电机俗称 马达 是无人机的动力来源 无人机通过改变电机的转速来改变无人机的飞行状态 即改变每个电机的速度 使得无人机能够盘旋空中 上升或
  • HTTP请求/响应报文结构

    HTTP请求报文 一个HTTP请求报文由四个部分组成 xff1a 请求行 请求头部 空行 请求数据 1 请求行 请求行由 请求方法字段 URL字段 和 HTTP协议版本字段 3个字段组成 xff0c 它们用空格分隔 比如 GET data
  • AStar寻路算法

    概述 AStar算法是一种图形搜索算法 xff0c 常用于寻路 他是以广度优先搜索为基础 xff0c 集Dijkstra算法和最佳优先 best fit 于一身的一种算法 示例1 4向 示例2 8向 思路 递归的通过估值函数找到最佳路径 x
  • ECCV2022 | 多模态融合检测新范式!基于概率集成实现多模态目标检测

    点击下方卡片 xff0c 关注 自动驾驶之心 公众号 ADAS巨卷干货 xff0c 即可获取 点击进入 自动驾驶之心技术交流群 后台回复 ECCV2022 获取ECCV2022所有自动驾驶方向论文 xff01 说在前面的话 标题 xff1a
  • 什么是地址映射

    ARM体系结构 地址映射 一 什么是地址映射 1 ARM Cortex A8架构 xff0c 32位CPU xff0c CPU设计时就有32根地址线和32根数据线 2 32根地址线决定了CPU的地址空间为4G xff0c 那么这4G空间如何
  • 寄存器和GPIO

    一 什么是GPIO 1 GPIO的英文全称General Purpose Input Output Ports 中文意思是通用I O端口 2 GPIO就是芯片的引脚 xff08 芯片上的引脚有些不是GPIO xff0c 只有一部分是 xff
  • 什么是I2C通信

    ARM体系 I2C通信 一 什么是I2C 1 I2C总线是由Philips公司开发的一种简单 双向二线制同步串行总线 它只需要两根线即可在连接于总线上的设备之间传送信息 2 主设备用于启动总线传送数据 xff0c 并产生时钟以开放传送的设备
  • 什么是AD转换

    ARM体系 ADC 一 什么是ADC 1 ADC xff1a analog digital converter xff0c AD转换 xff0c 模数转换 xff08 也就是模拟转数字 xff09 2 模拟信号只有通过A D转化为数字信号后
  • 什么是SPI通信

    ARM体系 SPI通信 一 什么是SPI通信 1 SPI是串行外设接口 Serial Peripheral Interface xff0c 可以理解为一种通信协议 xff0c 也就是用来传输数据的 2 SPI 是由摩托罗拉 Motorola
  • petalinux uboot源码在哪的问题

    petalinux uboot源码在哪的问题 提出问题解决问题注意 xff1a 要知道自己的版本 1 uboot2 kernel 提出问题 petalinux 源码目录存放在哪里的问题 xff0c 也就是petalinux工程的uboot和
  • petalinux uboot源码怎么打补丁

    petalinux的源码 petalinux工程对于我来说 xff0c 就是有一点不能直接起修改源码 xff0c 你需要间接的修改源码的内容 xff1f 这个修改你还需要遵从petalinux的规章流程 当你不知道的时候你会感到无从下手 x
  • 嵌入式Ubuntu根文件系统移植带桌面

    1 为什么要移植Ubuntu根文件系统 相信到达这一步的人 xff0c 都已经对根文件系统有了个充分的认识 xff0c 已经自己从无到有创建过根文件系统了 xff0c 玩过busybox的了 xff0c 也可能移植过别的嵌入式文件系统了 但
  • Keil5在已有项目中导入文件夹(文件或模块),后编译,头文件找不到问题

    Keil5在已有项目中导入文件夹 文件或模块 后编译 头文件找不到问题 如果你是以及成功导入文件后 但是编译时发现头文件找不到 可以直接从第二部分看起 第一部分导入文件 第二部分导入头文件 导入头文件时注意 注意 注意路径问题要和导入文件时

随机推荐