单片机与PC机多机通讯 串口通信

来源:互联网 发布:java数据库编程题型 编辑:程序博客网 时间:2024/05/01 07:33
下面是我写的一个实现多个下位机(单片机)与一个上位机(PC机)的一主多从串口通讯程序,用的STC89C52RC,定时器2做串口通信波特率发生器。
     实现功能是这样的:
     用调试助手向单片机发送一个数据包。
     通讯协议是这样的:
      数据包的格式如下所示(共10个字节组成):
0x2A,0xEB,0x8D,地址码,指令码,数据长度码,数据码,数据码,校验码,0xAD  
前面三个字节为帧头,即开始符。
地址码: 欲传送的目的地址,即选定哪一个单片机。
指令码:向单片机发送的指令
数据长度码: 用于指示后面有效数据的个数
数据码:传送的数据,配合指令码的纯数据。
校验码: 累加和校验,对地址码,指令码,数据长度码,数据码进行累加,用来检验数据的完整性和正确性。
0xAD : 帧尾,即结束符。

    本程序实现功能是这样的:
    用调试助手向单片机发送一个数据包,单片机收到后对数据解析,再回传指定的数据。
    例如发送:2a eb 8d 01 03 01 01 06 ad
指令码为01,单片机接收到后解析,回传0xce 0x7b 0x11 0xed。其中前两个字节为开始符,最后一个字节为结束符。同理,若收到的指令码为02,回传0xce 0x7b 0x12 0xed。以此模拟控制单片机操作。
若接收错误,即累加校验码不等于单片机实际计算的累加和,回传0xce 0x7b 0x02 0xed,提示接收错误,要求PC重发数据(模拟,需要上位机软件配合才行)。
单片机开机初始化后即向PC发送一个数据0xce 0x7b 0x00 0xed,用于指示单片机与PC通信已连接。

下面是程序:
#define ID 0x01 //单片机地址
uint8 rec_data;   //串口通信接收数据
uint8 state_flag=0;  //通信协议解析状态标志,初始化为0
uint8 retval=0;  //通信协议解析函数返回值,初始化为0
uint8 cmd;  //指令码
uint8 Data[2];  //数据码
uint8 data_count;  //数据长度码

程序大体思想是:
    首先定义了几个全局变量,接收到数据后,串口中断子程序中用变量rec_data存储一个字节的数据,随后对数据进行解析:首先判断数据包的完整性,正确性,然后提取指令码,数据码等数据,存放起来用于主程序处理。
    协议解析过程中,使用一个变量state_flag的全局变量作为协议解析状态标志,用于确定当前字节处于一帧数据中的那个部位,同时在接收过程中自动对接收数据进行校验和处理,在数据包接收完的同时也进行了校验的比较。因此当帧尾结束符接收到的时候,则表示一帧数据已经接收完毕,并且也通过了校验,关键数据也保存到了缓冲区(cmd和Data[])中。主程序即可通过查询retval的标志位来进行协议的解析处理。如果retval=1;   //错误标志,数据包传送不正确。如果retval=2;   //接收成功标志,数据包传送成功。
    接收过程中,只要哪一步收到的数据不是预期值,则直接将状态标志复位,用于下一帧数据的判断,避免状态自锁。
    以下是程序:
void PortInit();                //各端口初始化
void TimerInit();        //定时器初始化
void UsartInit();        //串口初始化
void usart_cmd_scan();        //串口命令扫描
void Data_analysis();   //通信协议解析函数
void Send(uint8 sendcmd);  //数据发送函数


/*--------------------------------        串口中断服务子程序 ------------------------------------*/
void ser() interrupt 4
{
   RI=0;
   rec_data=SBUF;   //读取接收到的数据
   Data_analysis();//数据解析  
}

/*
* 函数名:Data_analysis
* 描  述:通信协议解析函数
* 输  入:无
* 输  出:无
* 备  注:解析串口接收到的数据
/*--------------------------------        多机通信协议格式 ------------------------------------*/
/*  数据包的格式如下所示(共10个字节组成): */
/*  0x2A,0xEB,0x8D,地址码,指令码,数据长度码,数据码,数据码,校验码,0xAD  */
void Data_analysis()
{
   static uchar recdata_sum=0;  //存放累加和
   static uchar lencnt=0;  //数据长度计数器
   switch (state_flag)
     {
        case 0:
          {
             if(rec_data == 0x2A)     // 是否帧头第一个数据
               state_flag = 1;
             else
               state_flag = 0;    // 标志复位 
             break;      
          }
        case 1:
          {
             if(rec_data == 0xEB)     // 是否帧头第二个数据
               state_flag = 2;
             else
               state_flag = 0;    // 标志复位
             break;
          }
        case 2:
          {
             if(rec_data == 0x8D)     // 是否帧头第三个数据
               state_flag = 3;
             else
               state_flag = 0;    // 标志复位
             break;
          }
        case 3:
          {
             if(rec_data == ID)    // 判断目的地址是否正确
               {
                  state_flag = 4;
                  recdata_sum=rec_data;   //开始累加
               }   
             else
               state_flag = 0;   // 标志复位
             break;
          } 
        case 4: 
          {
             state_flag = 5;
             cmd=rec_data;  //指令码存储
             recdata_sum+=rec_data;  //累加
             break;
          }        
        case 5: 
          {
             lencnt = 0;  //数据长度计数器清零
             data_count=rec_data;  //数据长度码存储
             recdata_sum+=rec_data;  //累加
             if (data_count!=0)  //后面有数据码
               state_flag=6;
             else 
               state_flag=8;
             break;
          } 
        case 6: 
        case 7: 
          {
              Data[lencnt++]=rec_data;  //数据码保存
              recdata_sum+=rec_data;   //累加
              if(lencnt==data_count)
              {
                                          state_flag=8;
                                        lencnt = 0;        
                          }  
                                
              else
                state_flag=7;
              break;
          }
        case 8:
          {
             if(recdata_sum==rec_data)   //数据校验,判断累加和是否相等
               state_flag=9;
             else
               {
                  retval=1;   //置错误标志,数据包传送不正确。
                  state_flag=0;   
               }
                         recdata_sum=0;//累加和清零
             break;
          }
        case 9:
          {
             if (rec_data==0xAD)
               {
                                           retval=2;   //置接收成功标志,数据包传送成功。
                                        state_flag=0;
                           }
             else
               state_flag=0; 
             break; 
          }

     }
}

//主程序 , 不断扫描串口接收到的命令
void main()
{
        PortInit();                //各端口初始化
        TimerInit();        //定时器初始化
        UsartInit();        //串口初始化                                   
        Send(0xce);
        Send(0x7b);
        Send(0x00);
        Send(0xed);
        while(1)
        { 
                usart_cmd_scan();        //串口命令扫描
        }        
}


/*
* 函数名:usart_cmd_scan
* 描  述:串口命令扫描
* 输  入:无
* 输  出:无
* 备  注:扫描PC通过串口发送的命令
*/
void usart_cmd_scan()
{
        uchar sendcmd;   //下位机向PC发送的命令码
           switch (retval)
    {
        case 1:      //数据发送错误,请求PC重发
          { 
             sendcmd=2;  //向PC发送的重发数据命令,PC识别后向下位机重发数据包。
             Send(0xce);
                         Send(0x7b);
                         Send(sendcmd);
                         Send(0xed);  //向PC发送命令

                         retval=0;   //标志清零,防止重复扫描,重复执行。  2013/9/24
                         break;

          }
        case 2:      //数据发送成功,执行命令
          {
             switch (cmd)    //命令解码
                         {
                                 case 0x01:
                                {
                                        Send(0xce);
                                        Send(0x7b);
                                        Send(0x11);
                                        Send(0xed);
                                        cmd=0x00;
                                        break;
                                }
                                case 0x02:
                                {
                                        Send(0xce);
                                        Send(0x7b);
                                        Send(0x12);
                                        Send(0xed);
                                        cmd=0x00;
                                        break;
                                }
                                case 0x03:
                                {
                                        Send(0xce);
                                        Send(0x7b);
                                        Send(0x13);
                                        Send(0xed);
                                        cmd=0x00;
                                        break;
                                }                 
                         }
            }
                  retval=0;   //标志清零,防止重复扫描,重复执行。
     }
}


/*
* 函数名:Send
* 描  述:串口数据发送函数
* 输  入:sendcmd - 待发送的数据
* 输  出:无
* 备  注:
*/
void Send(uint8 sendcmd)
{
   ES=0;  //关闭串口
   SBUF=sendcmd;  //发送数据,向PC发送。
   while(!TI);
   TI=0;  //发送完成,TI清零
   ES=1;  //开串口 
}

以上是我写的这个程序,希望大家指点一下。
程序运行整体可以,但是有个问题,也希望大神们能帮忙看一下什么问题
每次在单片机关机后,再重新上电后,发送都没反应,只有手动按下开发板的复位键后才能正常通信,当再次断电上电后,又不行了,又得按复位键才正常。按说开发板上电就复位了呀,为什么还要手动复位才行?什么的问题?你们试一下这个程序有这个问题吗?
    还望大神们帮忙指点啊!
0 0
原创粉丝点击