打印
[其它应用]

实现MODBUS通信

[复制链接]
952|10
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
gongche|  楼主 | 2019-4-22 18:29 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
以前在STC15单片机上成功做过一款MODBUS-RTU的程序,现在把程序移植到使用中颖的SH79F161A,却老是通信不稳定,发几次数据就死机,频率是11.0592MHz,外部晶振,使用3个IO口:TXD,RXD和一个控制端口与SP485芯片连接。

使用特权

评论回复
沙发
huwr| | 2019-4-22 18:32 | 只看该作者
发送和接收函数做好,超时定时器处理好就可以了。

使用特权

评论回复
板凳
happy_10| | 2019-4-22 18:36 | 只看该作者
跟用什么单片机又没有关系,主要是软件框架的处理。

使用特权

评论回复
地板
bqyj| | 2019-4-22 18:39 | 只看该作者

楼主程序可以公开吗?贴程序看下吧,这么说看不出什么原因

使用特权

评论回复
5
gongche|  楼主 | 2019-4-22 18:42 | 只看该作者
//*****************************************************************************
// 包含文件
//*****************************************************************************
#include <SH79F161A.H>                   // SFR 声明
#include <intrins.H>
//*****************************************************************************
// 全局常量
//*****************************************************************************
#define UCHAR unsigned char
#define UINT  unsigned int
#define ULINT unsigned long int
typedef unsigned char BYTE;
typedef unsigned int WORD;
#define SUCCESS   0
#define FOSC 11059200L          //系统频率11.0592MHz

//*****************************************************************************
//定时器0初始化(用于定时)
//*****************************************************************************
void Timer0_Init(void)
{
    TCON1=0xF4;     //T0使用系统时钟的1/12作为时钟源,T1则使用1T模式
    //TL0=0xCD;      // //1T时的重装值
    //TH0=0xD4;
    TL0=0x66;      // //12T时的重装值
    TH0=0xFC;
    TF0=0;
    TR0   = 1;
    ET0   = 1; //开中断T0
}

//*****************************************************************************
//定时器1初始化{用于波特率)
//*****************************************************************************
void Timer1_Init(void)
{
    SCON=0X50;     //串口方式1,8位数据,可变波特率,允许串行接收
  TMOD = 0x21;   //定时器1为模式2(8位自动重载,用于产生波特率)(备注:同时T0则为模式1即:16位不可重载)
  TL1 = (256 - (FOSC/32/BaudValue));   //设置波特率重装值
  TH1 = (256 - (FOSC/32/BaudValue));
  TR1 = 1;                    //定时器1开始启动
  ES0 = 1;                     //使能串口中断
  RS485_CON = 0;      //处于接收
}

//-------------------------------定时器0 1ms 中断 -------------------------
void timer0_IntProc() interrupt 1
{
    //TL0=0xCD;      //1T时的重装值
    //TH0=0xD4;
    TL0=0x66;      // //12T时的重装值
    TH0=0xFC;
  bt1ms = 1;
}


//--------------------------------串行中断程序---------------------------
void comm_IntProc() interrupt 4
{
        if (RI)
    {
         RI = 0;
       receTimeOut = 10;    //通讯超时值这个地方很重要  10ms
       receBuf[receCount] = SBUF;
       receCount++;          //接收地址偏移寄存器加1
    }
    if (TI)
    {
        TI = 0;                 //清除TI位
        busy = 0;               //清忙标志
    }
}   

//------------------------------------定时处理--------------------------------
void timeProc(void)
{
        if(bt1ms)
        {
           bt1ms = 0;
          if(receTimeOut>0)
            {
             receTimeOut--;
            if(receTimeOut==0 && receCount>0)   //判断通讯接收是否超时
                {
                 RS485_CON = 0;   //将485置为接收状态
                 receCount = 0;   //将接收地址偏移寄存器清零
                 }
             }
        }
}

void uartsends(UCHAR buff[],UCHAR len)
{
    UCHAR i;
    for(i=0;i<len;i++)
    {
        SBUF=buff[i];
        while(!TI);
        TI=0;
    }
}

/*******************************串口发送函数 ********************************/
void Begin_send(void)
{
    RS485_CON = 1;   //处于发送
    uartsends(sendBuf,sendCount);
    RS485_CON = 0;   //发送完后将485置于接收状态
  receCount = 0;   //清接收地址偏移寄存器
}

/*************************查询uart接收的数据包内容函数 **************************/
////函数功能:从机根据串口接收到的数据包receBuf[1]里面的内容,即function code执行相应的命令
/********************************************************************************/
void checkComm0Modbus(void)        //10ms内必须响应接收数据
{
UINT crcData;
UINT temp;

if(receCount > 4)        //如果接收到数据
{   
  switch(receBuf[1])//功能代码
  {
    case 3:     //功能码3:读取寄存器(一个或多个)
    {   
       if(receCount >= 8)  //从询问数据包格式可知,receCount应该等于8  ,接收完成一组数据应该关闭接收中断
       {     
          if(receBuf[0]==LocalAddr)   //核对地址
          {
              crcData = crc16(receBuf,6); //核对校验码,重新计算校验码
              temp=receBuf[7];//获取发送来的原校验码
              temp=(temp<<8)+receBuf[6];
              if(crcData == temp)//与原校验码比较
              if(receBuf[1] == 3)//核对功能码
              {
                readRegisters();            //读取保持寄存器(一个或多个)
              }

              
                  receCount = 0;                                      
          }      
        }      
        break;
    }

   
    case 6: //功能码6:写寄存器
    {
        if(receCount >= 8)//从询问数据包格式可知,receCount应该等于8   ,接收完成一组数据应该关闭接收中断
      {   
         if(receBuf[0]==LocalAddr)//核对地址
         {
           crcData = crc16(receBuf,6);//核对校验码
               temp=receBuf[7];
             temp=(temp<<8)+receBuf[6];
           if(crcData == temp)
           if(receBuf[1] == 6)//核对功能码
           {
             presetSingleRegister();      //预置单个保持寄存器
           }
           receCount = 0;
         }
      }
      break;
   }
   default: break;
  }
}
}

//*******************************************************************************
//主程序
//*****************************************************************************
void main (void)
{
    UCHAR   j;

    EA=0;
    Delay_ms(15);
    Timer0_Init();
    Timer1_Init();
     EA=1;
            
  while(1)
    {
      WatchDog();
      timeProc();
        checkComm0Modbus();
      }
}

使用特权

评论回复
6
gongche|  楼主 | 2019-4-22 18:45 | 只看该作者
以上程序,我没有上CRC校验,以及相关的功能代码处理函数,因为这部分都是成熟的代码,不会出问题,关键是通信帧起始和帧结束判断处理部分容易出错。

使用特权

评论回复
7
sszxxm| | 2019-4-27 18:17 | 只看该作者
我晕,居然用查询方式的循环发送。都什么年代了啊。

另外串口接收这里一定要加一个缓冲区防止溢出的处理,也就是你要保证每次接收的数据存放到定义的缓冲区位置,不能超出了自己还不知道,变量不停++,最后变成覆盖其它变量,这样就非常容易死机了。我的习惯是用一个宏定义来定义缓冲区的长度,比如BUF_LONG,每次变量++后,做一次这样的处理:receCount %= BUF_LONG;这样保证数据永不越界。

使用特权

评论回复
8
小傻蛋| | 2019-4-28 15:47 | 只看该作者
好   太好了 楼主

使用特权

评论回复
9
小傻蛋| | 2019-4-28 15:49 | 只看该作者
下下

使用特权

评论回复
10
小傻蛋| | 2019-4-28 15:49 | 只看该作者
谢谢

使用特权

评论回复
11
yzq13246068880| | 2019-4-29 08:38 | 只看该作者
顶一个

使用特权

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

本版积分规则

768

主题

9410

帖子

2

粉丝