找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 6633|回复: 7
收起左侧

我写的一个实现单片机与PC机多机通讯的程序 串口通信

  [复制链接]
ID:102668 发表于 2016-1-14 00:10 | 显示全部楼层 |阅读模式
   下面是我写的一个实现多个下位机(单片机)与一个上位机(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;  //开串口
}

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

回复

使用道具 举报

ID:102611 发表于 2016-1-15 09:21 | 显示全部楼层
复位的时间不同步    时序问题吧      我刚学单片机   这是我自己的理解
回复

使用道具 举报

ID:19715 发表于 2016-1-30 11:59 | 显示全部楼层
每一个数据包在接收的时候,要加上超时约定,超过这个约定时间就要重新来
回复

使用道具 举报

ID:169871 发表于 2017-12-13 16:58 | 显示全部楼层
发送数据后延时
回复

使用道具 举报

ID:156540 发表于 2018-12-12 22:20 | 显示全部楼层
明白 发表于 2016-1-30 11:59
每一个数据包在接收的时候,要加上超时约定,超过这个约定时间就要重新来

你好,请问这个超时约定应该怎么写呢?要用到定时器中断吗
回复

使用道具 举报

ID:556672 发表于 2019-6-6 16:56 | 显示全部楼层
写得不错,楼主有crc16校验的例程么。
回复

使用道具 举报

ID:43342 发表于 2019-6-7 09:12 | 显示全部楼层
开机收不到连接信息,再次发送连接
回复

使用道具 举报

ID:106272 发表于 2021-4-1 20:33 | 显示全部楼层
mark一下,有时间仔细学学
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表