某款扫地机器人源码分析1——指令数据解析处理部分

前言

本文对一款基于stm32实现的扫地机器人程序进行分析。
链接: 某款stm32扫地机器人程序

扫地机器人通过串口接收或发送数据,数据包括控制端向扫地机器人发送的控制指令,扫地机器人向控制端反馈当前状态等。
从串口接收数据里解析出来的数据放在FR_Rx变量中,其结构体为:

/*帧接收记录*/
__packed typedef struct
{
    Frame_t tFrame;//帧信息
    u8 DataBuf[FRAME_DATA_MAX_LENGTH];//帧数据
    u8 NextFrameSeq;//下次应该的序列
    
    u8 ContinueErrNum;//连续错误次数
    bool IsStartReceive;//是否开始接收
    bool IsFrameNotProcess;//帧数据还未处理
    FrameErr_e FrameErrType;//帧错误类型
}Rx_FrameRecord;

这是控制端向扫地机器人发送的数据。

向控制端发送的数据,在放入串口发送缓冲区之前先放在变量FR_Tx里,其结构体为:

/*帧发送记录*/
__packed typedef struct
{
    Frame_t tFrame;//帧信息
    u8 DataBuf[FRAME_DATA_MAX_LENGTH];//帧数据
    u8 NextFrameSeq;//下次应该的序列
    bool IsSendOut;//是否发送了一帧数据?
    bool IsReceiveRespond;//是否接受到回应
}Tx_FrameRecord;

这是扫地机器人向控制端发送的数据。
由于FR_RxFR_Tx是全局变量,可见整个系统同一时刻只处理一条接收数据或者发送数据。

对串口接收数据解析,或者对串口发送数据组帧的任务都在Protocol文件夹里。

FrameHandle.c

void vFrameDataHandlerTask(void *p)

帧处理函数任务,将串口接收到的数据解析为协议帧数据。
处理步骤:
1、查看串口1缓冲区是否有新接收到的数据,并且FR_Rx里没有未被处理的接收数据,符合条件则对串口1接收数据进行解析。
2、从串口1接收缓冲区里对数据进行解析,执行代码为:

eFrame_Analy(ucUSART1ReceiveBuffer[ucUSART1ReadBufferIndex],length,&FR_Rx);//解析数据

其中
ucUSART1ReceiveBuffer[ucUSART1ReadBufferIndex]是串口1接收数据存放缓冲区。
length是该缓冲区中数据长度。
FR_Rx是解析出来的有效数据存放的接收数据帧。
3、判断接收到的数据帧类型不是回复帧,就通过

ucCode_RespondFrame(&FR_Tx,&FR_Rx,ucUSART1TrainsmitBuffer[ucWtiteDataToUSART1TransmitGrooveIndex]);

组一个接收数据的回复帧,并放到串口发送缓冲区中等待发送。

4、如果判断到接收数据帧里此刻有未被处理的数据帧,则根据帧类型进行分类数据。
5、调用

vFrameAutoResendByUsart1(&FR_Rx);

判断发送数据是否有回复帧,如果回复帧表示需要重发,或者超时未接收到回应帧(回复帧),则进行重发。
源码:

void vFrameDataHandlerTask(void *p)
{
    u8 length=0;
    while(1)
    {
        /*串口接收到数据 并且帧数据已经全部处理完毕*/
        if(ucUSART1_ReceiveMessageNumber && FR_Rx.IsFrameNotProcess == false)
        {
            length=ucUSART1ReceiveBufferLength[ucUSART1ReadBufferIndex];//获取接收数据的长度
            eFrame_Analy(ucUSART1ReceiveBuffer[ucUSART1ReadBufferIndex],length,&FR_Rx);//解析数据
            USART1ReceiveDataReadDone(  );//串口数据读取完毕
            
            /*成功接收到的不是回应帧*/
            if( FR_Rx.tFrame.FrameFunction != ffRespond  )
            {
                /*配置回应帧*/
                ucCode_RespondFrame(&FR_Tx,&FR_Rx,ucUSART1TrainsmitBuffer[ucWtiteDataToUSART1TransmitGrooveIndex]);
                /*发送回应帧到串口发送缓冲区*/
                ucUSART1TrainsmitLength[ucWtiteDataToUSART1TransmitGrooveIndex]=5+FR_Tx.tFrame.DataLength;
                WriteDataToUSART1TraismitBufferDone();
            }              
        }
        
        /*帧数据处理*/
        if(FR_Rx.IsFrameNotProcess)
        {
            switch(FR_Rx.tFrame.FrameFunction)
            {
                case ffRespond: //接收到的是回应帧
                    FR_Tx.IsReceiveRespond=true;//表示接收到回应帧
                    break;
                
                case ffCommand: //接收到的是命令帧
                    vCommandFrameHandle(&FR_Rx);
                    break;
                
                case ffData: //接收到的是数据帧
                    vDataFrameHandle(&FR_Rx);
                    break;
                
                case ffIdle: break;//接收到的是空闲帧,不处理
                case ffMax: break;
            }
            FR_Rx.IsFrameNotProcess=false;//标记数据已经处理
        }
        vFrameAutoResendByUsart1(&FR_Rx);
        vTaskDelay(5);
    }
}
void vCommandFrameHandle(Rx_FrameRecord *R)

命令帧处理函数,控制端在向扫地机器人发送数据帧前,会先发送一个命令帧,表明接下来发送的数据帧是什么类型。
所以根据命令帧类型对g_tLastCommand变量进行赋值,在数据帧处理时使用。
源码:

void vCommandFrameHandle(Rx_FrameRecord *R)
{
    
    if( R->tFrame.DataLength==1 )//命令字节只有一字节
    {
        switch(R->tFrame.FrameData[0])//查看命令
        {
            case UpdateFirmwareStart: //是开始更新固件命令
                g_tLastCommand=UpdateFirmwareStart;//标记命令
                ProductAppPassword=0;//产品密钥清理
                STMFLASH_Write(PASSWORD_ADDR,(u16*)&ProductAppPassword,2);//擦除产品秘钥
                ((void (*)())(*((u32 *)(0x08000004))))();//复位 进入bootloader程序
                break;
            
            case UpdateFirmwareDone: //是结束更新固件命令 firmware程序不会用到
                g_tLastCommand=UpdateFirmwareDone;
                break;
            
            case BehaviorControl: //是行为控制命令
                g_tLastCommand=BehaviorControl;//记录命令
                break;
            
            case RequestState://请求状态命令
                g_tLastCommand=RequestState;
                vRequestStateCommandHandle(&FR_Tx);//处理 请求状态命令
                break;
            
            case RouteCoord://线路坐标命令
                g_tLastCommand=RouteCoord;
                break;
        }
        
    }
}

void vDataFrameHandle(Rx_FrameRecord *R)

数据帧数据处理函数。根据g_tLastCommand变量,判断当前数据帧是什么类型数据,并调用相应的函数进行处理。
源码:

void vDataFrameHandle(Rx_FrameRecord *R)
{
    switch(g_tLastCommand)
    {
        case UpdateFirmwareStart://命令为开始更新固件 不会执行
            break;
        
        case UpdateFirmwareDone://命令为固件更新完毕 不会执行
            break;  
        
        case RequestState://命令为请求状态 不会执行
            break;
        
        case BehaviorControl://命令为行为控制
            vBehaviorControlHandle(R);//运行 行为控制处理函数
            break;

        case RouteCoord://线路坐标命令
            vRouteCoordHandle(R);//运行 线路坐标处理函数
            break;        
    }
}

void vBehaviorControlHandle(Rx_FrameRecord *R)

在数据帧处理函数中判断出该数据帧为行为控制指令,则调用该函数进行处理。
首先判断出行为控制指令的类型:停止前进后退原地转弯向左绕轴转弯向右绕轴转弯。然后对g_sExpect_Angle变量和g_tRobotState.Robotstate.eDirection变量进行赋值。
vBehaviorManageTask任务里会根据g_tRobotState.Robotstate.eDirection变量得知现在扫地机器人要执行什么动作,并结合g_sExpect_Angle变量设置机器人两个轮子的速度。
源码:

void vBehaviorControlHandle(Rx_FrameRecord *R)
{
    BehaviorControl_t *pBC = (BehaviorControl_t *)R->DataBuf;//数据赋值给结构体成员
    
    switch(pBC->Com)//控制类型
    {
        case bccStop ://停止  
            g_tRobotState.Robotstate.eDirection = zStop;
            break;
        
        case bccForward ://前进
            
            g_sExpect_Angle = pBC->TargetAngle*10;//获取目标角度 
            g_tRobotState.Robotstate.eDirection = zForward;
            break;
        
        case bccRetreat ://后退
            g_sExpect_Angle = pBC->TargetAngle*10;//获取目标角度 
            g_tRobotState.Robotstate.eDirection = zRetreat;
            break;
        
        case bccTurnRotate ://原地转弯
            g_sExpect_Angle = pBC->TargetAngle*10;//获取目标角度 
            g_tRobotState.Robotstate.eDirection = zRotate;
            break;
        
        case bccTurnLeft ://向左绕轴转弯
            g_sExpect_Angle = pBC->TargetAngle*10;//获取目标角度   
            g_tRobotState.Robotstate.eDirection = zTurnLeft;
            break;
            
        case bccTurnRight ://向右绕轴转弯
            g_sExpect_Angle = pBC->TargetAngle*10;//获取目标角度 
            g_tRobotState.Robotstate.eDirection = zTurnRight;
            break;
    }
}

void vRouteCoordHandle(Rx_FrameRecord *R)

接收到路线坐标数据的处理函数,这款扫地机器人没用到路线坐标。
源码:

void vRouteCoordHandle(Rx_FrameRecord *R)
{
    /*每次都会接受到5个 坐标点 。第一个是当前机器人坐标 一个 坐标X Y为8字节*/
    memcmp(&g_tRouteCoord,R->DataBuf,5*8);
}

void vRequestStateCommandHandle(Tx_FrameRecord *T)

接收到数据为请求状态的处理函数 ,请求状态数据是指控制端向机器人索要状态数据,控制端发一帧请求状态数据,机器人返回一帧状态数据。
对机器人的状态数据进行组帧,并放到串口数据发送缓冲区中。
源码:

void vRequestStateCommandHandle(Tx_FrameRecord *T)
{
    s16 loc_CurrentAngle;//当前角度
    u16 loc_Size;//用于记录数据大小
    
    if(T->IsSendOut == false)//若有数据发送 但是没有收到回应,则等待接收回应帧
    {   
        /*写入机器人全局信息*/
        loc_Size=sizeof g_tRobotState;
        memcpy(T->DataBuf,(void *)&g_tRobotState,loc_Size);

        /*写入机器人当前角度信息*/
        loc_CurrentAngle=CURRENT_ANGLE;
        loc_Size=sizeof loc_CurrentAngle;
        memcpy(T->DataBuf+sizeof g_tRobotState,&loc_CurrentAngle,loc_Size);
        
        /*写入数据到串口BUF 等待串口发送*/  
        loc_Size=(sizeof g_tRobotState)+(sizeof loc_CurrentAngle);//获取总数据字节
        if(loc_Size > FRAME_DATA_MAX_LENGTH)
        {
            loc_Size=FRAME_DATA_MAX_LENGTH;//数据溢出 标记为发送最大字节
            DEBUG("Tx Frame Over!\r\n");//输出信息
        }
        ucCode_DataFrame(&FR_Tx,T->DataBuf,loc_Size,ucUSART1TrainsmitBuffer[ucWtiteDataToUSART1TransmitGrooveIndex]); 
        ucUSART1TrainsmitLength[ucWtiteDataToUSART1TransmitGrooveIndex]=loc_Size+5;//数据字节+5个字节帧信息
        WriteDataToUSART1TraismitBufferDone();
        
        T->IsSendOut=true;//标记 已经发送了一帧数据
    }
}

void vFrameAutoResendByUsart1(Rx_FrameRecord *R)

1、根据发送数据帧FR_Tx的变量IsSendOut判断是否有数据发送出去了,如果有执行接下来的处理,没有则返回。
2、如果判断到回复帧,则把发送数据帧FR_Tx里的一些状态进行清除。
3、如果判断到超过1秒没收到回复帧,或者回复帧内容错误,则重发一次指令,并且发完清除
FR_Tx里的一些状态,也就是对于超时没有回复帧的数据只重发1次。

源码:

void vFrameAutoResendByUsart1(Rx_FrameRecord *R)
{
    static  bool  IsFirstIn=true;
    static  OS_TimeStruct TimFrameAutoResend;
    static  bool  IsRespondErr=false;
    
    if(FR_Tx.IsSendOut == true)//有发送数据
    {
        if(true == IsFirstIn)
        {
            TimFrameAutoResend=GetOSRunTimeNow();//第一次进入  获得进入时间
            IsFirstIn = false;
        }
        
        if(FR_Tx.IsReceiveRespond == true)//接收到回应帧
        {
            //回应帧反馈的是 帧正确
            if(R->tFrame.FrameFunction == ffRespond && R->tFrame.FrameData[0] == FrameSuccess)
            {
                FR_Tx.IsReceiveRespond=false;//清理 接收到回应帧
                FR_Tx.IsSendOut=false;//清零发送标志位
                IsFirstIn= true;//;重新标记第一次进入
            }else//数据有误 需要重新发送
            {
                IsRespondErr= true;
            }
        }
        /*超时还没有接收到回应帧 1秒 或者回应帧表示数据出错*/
        if(TimeOut == MultipleTimeoutCheck( &TimFrameAutoResend,0, 5, 0 ) || IsRespondErr)
        {
            /*发送指针回移动一位  重新发送刚刚缓存区的数据*/
            ucDMA1_Stream4TransmitGrooveIndex = ( (ucDMA1_Stream4TransmitGrooveIndex-1) > \
            USART1_TRANSMIT_GROOVE_SIZE ) ? USART1_TRANSMIT_GROOVE_SIZE : (ucDMA1_Stream4TransmitGrooveIndex-1);
            
            ucUSART1_TransmitMessageNumber++;//串口发送数据加1  表示重发数据
            
            FR_Tx.IsReceiveRespond=false;//清理 接收到回应帧
            FR_Tx.IsSendOut=false;//清零发送标志位
            IsFirstIn= true;//;重新标记第一次进入
        }
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值