打印
[51单片机]

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

[复制链接]
2703|13
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
sdutkqb|  楼主 | 2013-10-13 17:06 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
    下面是我写的一个实现多个下位机(单片机)与一个上位机(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;  //开串口
}

     以上是我写的这个程序,希望大家指点一下。
程序运行整体可以,但是有个问题,也希望大神们能帮忙看一下什么问题
每次在单片机关机后,再重新上电后,发送都没反应,只有手动按下开发板的复位键后才能正常通信,当再次断电上电后,又不行了,又得按复位键才正常。按说开发板上电就复位了呀
    还望大神们帮忙指点啊!
沙发
crjab| | 2013-10-14 07:52 | 只看该作者
好贴就要顶一顶~~~

使用特权

评论回复
板凳
Frank2013| | 2013-10-14 08:50 | 只看该作者

使用特权

评论回复
地板
zhuwenwujy| | 2014-7-17 13:43 | 只看该作者
强帖顶一下

使用特权

评论回复
5
星夜之北| | 2014-11-21 09:30 | 只看该作者
楼主:你的串口用的是方式几?我也刚好在弄PC机与单片机多机通信!
但是我看到别人在PC机与单片机485多机通信时,单片机串口用的是方式一!这怎么可能呢???串口方式一只适用于点对点通信啊!!只有方式2、3才能用于多机通信啊!但是方式2、3在于PC机通信时,第九位地址数据标志位又没办法解决!!如果强行用方式一多机通信,那么一个从机在接收消息时,其他从机也在不断接收数据和自己的地址对比,就一直在串口中断中,这样肯定不行啊,但是人家那系统还能运行!太神奇了!是不是单片机的速度比较快,即使不断中断也没啥影响????请大神们指点指点!!

使用特权

评论回复
6
ss_123| | 2014-12-21 09:43 | 只看该作者
本帖最后由 ss_123 于 2014-12-21 09:46 编辑

用串口助手试了一下,为什么有时候要向单片机发送两次命令,单片机才返回一次数据,是什么原因,请诸位大神解释一下?

使用特权

评论回复
7
hnkf118| | 2014-12-21 14:58 | 只看该作者
你这个太的 比较复杂了。

使用特权

评论回复
8
hnkf118| | 2014-12-21 15:00 | 只看该作者
   
u8_t temp_uart_recv (void)
{
    u16_t  crc_rx;
    u16_t  crc;  
    u16_t  temp;   
    u8_t   dat;
    u8_t   err;


    switch ( stUartRx.step ) {                            // 分时处理接收数据
    case 0 :                                              // --缓存初始化
        memset(stUartRx.buf, 0, sizeof(stUartRx.buf));    // 清除缓存
        stUartRx.index = 0;                               // 复位缓存索引
        stUartRx.step  = 1;                               // 进入下一步接收数据
        break;

    case 1 :                                              // --等待数据到达
        if ( UartRxGetNMsgs(3) == 0 ) {                   // 检测现在接收缓存里面的数据数量
            return 1;                                     // 没有数据 退出处理
        } else {
            stUartRx.step = 2;                            // 进入下一步
        }
        break;

    case 2 :                                              // -- 接收数据
        do {
            dat = UartRxGetChar(3, &err);                       // 读取数据字节
            stUartRx.buf[stUartRx.index]  = dat;              // 数据进本地缓存
            if (stUartRx.index == 0)                          // 第1字节?
            if (stUartRx.buf[0] != 0xff) {                    // 头1?
                stUartRx.step  = 0;                           // 不是 错误 重新接收数据包
                return 1;                                     // 退出后续处理
            }
            if (stUartRx.index == 1)                          // 第2字节?
            if (stUartRx.buf[1] != 0xfe) {                    // 头2
                stUartRx.step  = 0;                           // 不是 错误 重新接收数据包
                return 1;                                     // 退出后续处理
            }

            stUartRx.index += 1;                              // 指向下一字节
            if (stUartRx.index >= sizeof(stUartRx.buf)) {     // 接收完毕?
                stUartRx.index = 0;                           // 复位接收索引计数器
                stUartRx.step  = 3;                           // 进入下一步处理
            } else {
                stUartRx.step  = 1;                           // 继续等待新的数据到来
            }
        }  while (UartRxGetNMsgs(3) != 0);
        break;

    case 3 :                                              // -- 处理校验
        stUartRx.step = 0;
   
        crc      = check_crc16(stUartRx.buf, 11);
        crc_rx   = stUartRx.buf[11];                                       // 发送的校验和
        crc_rx <<= 8;                                                //
        crc_rx  += stUartRx.buf[12];                                       //
   
        if (crc_rx != crc ) {
            temp_rx_err_cnt++;
        
            temp_rx_pack_err++;                                      // 错误数据包处理
            if (temp_rx_pack_err > 60) {                            // 连续接收数据包20包错误
                temp_rx_pack_err = 0;                                // 复位计数器
            
                temp_set_sts( TEMP_CH_MAIN, TEMP_ERR_FLAG_RX_TMO);    //
                temp_set_temp(TEMP_CH_MAIN, TEMP_ERR_RX_CRC);        // 接收数据包错误 温度显示为1650
            }
            return 1;
        }
        
        switch ( stUartRx.buf[2] ) {                        
        case 0x01 :                                           // --温度板数据?
            temp   = stUartRx.buf[ 3];
            temp <<= 8;      
            temp  |= stUartRx.buf[ 4];        
            temp_set_temp(TEMP_CH_MAIN, temp);                // 得到温度
            if (temp >= TEMP_ERR_TEMP_BAS) {
                __NOP();
            }

            OS_ENTER_CRITICAL();                              // 进入临界处理
            temp_rx_pack_cnt += 1;                            // 正确接收到温度数据包计数器
            temp_rx_pack_err  = 0;                            // 复位校验数据包错误计数器
            temp_rx_tmo       = 0;                            // 正确接收到数据后 清除接收数据超时计时器
            OS_EXIT_CRITICAL();                               // 退出临界处理
            break;

        case 0x02 :                                           // --风扇板
            temp = stUartRx.buf[9];
            fan_set_sts(FAN_CTRL_TOP, temp);                  // 风扇状态信息
            break;

        default :                                             // --
            break;
        }   
        break;

    default :                                             // 错误步骤
        stUartRx.step      = 0;                           // 复位到第0步骤 接收下一包数据
        stUartRx.index     = 0;                           // 复位数据索引计数器 准备接收下一数据包
        stUartRx.flag_temp = FALSE;                       // 清除接收到一包数据的标志
        stUartRx.flag_fan  = FALSE;                       //
        break;   
    }
   
    return 0;
}

使用特权

评论回复
9
hnkf118| | 2014-12-21 15:01 | 只看该作者
参考这样的结构, 适用性更强。

使用特权

评论回复
10
coody| | 2014-12-21 15:01 | 只看该作者
我都是用MODBUS-RTU做多机通讯。

使用特权

评论回复
11
YingziSeek| | 2014-12-21 15:05 | 只看该作者

使用特权

评论回复
12
hnkf118| | 2014-12-21 17:18 | 只看该作者
是的MB协议就是多从机协议的。 这个是从机的实现。 贴图说明。。。

mb协议000.jpg (231.4 KB )

mb协议000.jpg

mb.jpg (202.54 KB )

mb.jpg

MB002.jpg (183.43 KB )

MB002.jpg

MB003.jpg (54.46 KB )

MB003.jpg

MB004.jpg (251.49 KB )

MB004.jpg

MB005.jpg (145.41 KB )

MB005.jpg

使用特权

评论回复
13
joyme| | 2014-12-22 15:44 | 只看该作者

丢包很正常,你并没有全盘考虑接收和发送,上位机的发送和单片机的发送可能存在冲突,导致单片机接收不到数据(ES=0串口中断不会触发)

使用特权

评论回复
14
金成范| | 2014-12-23 21:56 | 只看该作者
好贴,支持

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

8

主题

42

帖子

2

粉丝