搜索
bottom↓
回复: 36

开源PLC学习笔记14(MODBUS 入门)——2013_12_01

  [复制链接]

出0入0汤圆

发表于 2013-12-1 15:56:40 | 显示全部楼层 |阅读模式
前13节笔记都是分析的单机工作的仿三菱PLC,只实现了一个简单的功能,即使后面使用STM32把开源PLC的功能都加上,也是一个没有竞争力的简单产品。而重要的原因就是缺少MODBUS,可以让很多PLC联机工作的协议。

了解了一下MODBUS协议(第一阶段不含TCP),感觉和学习三菱PLC通讯协议有点像,决定用同样的方法,找代码边理解边学习,并给上节的程序加入modbus协议,能让该程序和组态王或者Eview触摸屏通讯。

选择入门MODBUS,除了该协议真得很重要外,另外一个更重要的原因是入门资料还算丰富。
一些基本概念如下图,





然后,网上找到仿真,感受一下就可以了。


下一步就要选择入门的程序了,
http://www.amobbs.com/forum.php? ... mp;highlight=modbus
这个可以用PROTEUS仿真,比较方便,下一步就是分析这个例子。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2013-12-2 08:34:22 | 显示全部楼层
要理解上面的程序,对我来说,还需要以下几个方面的知识:
1、RS485工作原理,包括收发信息和地址确定。
2、CRC检验
3、ADC0831

我的处理方法:
首先CRC检验有非常多的函数网上可以借鉴,而且如果展开的话会有比较多的内容,耽误这里对MODBUS的入门,所以这里把CRC当成可以直接利用的函数即可,对它的理解放在MODBUS入门之后。

ADC0831同上处理。

只有RS485需要先理解一下,虽然学单片机时曾经看过一个例子。
有了准备工作思路,就可以开始了。

*******************************************

搜一下,http://zhidao.baidu.com/question/152420569.html?qbl=relate_question_0。(
因为RS232比较熟悉,对比学习效果更佳,
与 RE/DE 连接在一起的那条单片机I/O, 我命名为 CTRL_PIN ,至于是哪条,你才知道.

其实发送与接收与普通的 232 有 99.9% 的相似, 只是多了两行,看下面

发送程序时:
CTRL_PIN = 1; // 输出高电平,允许MAX485输出差分信号.
UART232_SENDCHAR();   // 普通232发送函数
CTRL_PIN = 0; // 输出低电平,禁止发送,允许接收.


接收程序时: 只要保证 CTRL_PIN = 0 就可以了,无论你是采用中断,还是沦询的方式接收字符,与普通串口用法一样


基本上可以这样理解 RS485 = RS232 + CTRIL_PIN 。

再看一个例子,确认 RS485 = RS232 + CTRIL_PIN


MODBUS程序使用MAX487,区别大吗?
http://zhidao.baidu.com/question/22510856.html
485速率高2.5MBPS
487速率低0.25MBPS

485驱动32个点
487驱动128个点

485静态电流300UA
487静态电流120UA
工作原理一致,只是硬件参数有区别。

***********************************************************
下一步准备工作是把从设备程序分成模块,原来的程序是一个大文件,现在按照笔记10分成几个模块。划分模块的同时,完成了对程序的初步理解。
从设备文件管理后,


因为程序注释程度不够,修改了几个名称,提高些可读性。

原程序有个警告,


先搜一下,
http://www.cnblogs.com/Curiosity ... /12/25/2301090.html
这个问题必须注意,可能引起程序冲突,假设你用于自动化领域,则可能导致信号产生尖峰。 产生这一警告的一个根源是:你在主循环 里调用了一个函数(如aaa),而在中断服务函数里,你用调用了这个函数(如aaa)。这样当主循环运行到该函数中 是,一旦产生中断,则在中断里又再次调用该函数!这时,很可能出错! 避免这种情况的方法很多:如,在进中断的时候置需调用该函数的标志,而在主循环中调用该函数。

开源PLC的FX1NProcessing函数并没有放在中断函数里,而是采用了上面文章推荐的方法。理解这个MODBUS例子后,再修改这个程序。

准备工作差不多了,就可以开始理解从设备的程序了,先选从设备因为感觉从设备程序简短些。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入85汤圆

发表于 2013-12-2 09:28:04 | 显示全部楼层
很好,顶一下

出0入0汤圆

 楼主| 发表于 2013-12-2 10:15:35 | 显示全部楼层
本帖最后由 oldbeginner 于 2013-12-2 10:24 编辑
oldbeginner 发表于 2013-12-2 08:34
要理解上面的程序,对我来说,还需要以下几个方面的知识:
1、RS485工作原理,包括收发信息和地址确定。
2 ...
先把主函数简化一下,这里要解决RS485多机的地址问题。
  1. void main(void)
  2. {
  3.         局部变量定义。。。
  4.         变量赋值。。。。
  5.      node=!HighNode;
  6.         node<<=1;
  7.        node|=!LowNode;
  8.        modbus[0]=node;
  9.         
  10.         InitUART();                        
  11. 。。。。
  12.         while(1)
  13.         {
  14.         。。。。。
  15.         }
  16. }
复制代码



这个例子采用了硬件决定地址的方式,

分析从设备2,
node 是uchar类型,记为0000 0000
node=! P3^3;         变为0000 0001
node<<=1;            变为0000 0010
node|=!P3^2;        变为0000 0010

因为地址采用硬件识别,从设备不需要在软件里设置地址,可以使用同一个程序,对接非常多从机的系统来说可以减少误操作。

*******************************************
然后就是串口初始化,
  1. void InitUART(void)
  2. {
  3.     TMOD = 0x20;
  4.     SCON = 0x50;
  5.     TH1 = 0xFE;
  6.     TL1 = TH1;
  7.     PCON = 0x00;
  8.     EA = 1;
  9.     ES = 1;
  10.     TR1 = 1;
  11.         
  12.         TMOD |= 0x01;
  13.     TH0 = 0x3C;
  14.     TL0 = 0x0B0;  // timer sets at 20ms
  15.     ET0 = 1;
  16.     TR0 = 1;
  17. }
复制代码
有疑惑的是SCON
这里SCON还是0x50,


这里并没有选择SM2=1,多机通信,什么原因?
网上暂时没有找到合适的解释,感觉是SM2=1自动找地址,而本程序MODBUS报文中含有地址,不需要SCON来找地址。网上看了一下,modbus通讯基本上SCON=0x50。




本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2013-12-2 11:08:35 | 显示全部楼层
oldbeginner 发表于 2013-12-2 10:15
先把主函数简化一下,这里要解决RS485多机的地址问题。


在继续主函数之前,先看一下中断函数,
  1. void UARTInterrupt(void) interrupt 4
  2. {
  3.     if(RI)
  4.     {
  5.         RI = 0;
  6.                 mdproc(SBUF);
  7.     }
  8.     else
  9.         TI = 0;
  10. }
复制代码


**********************************************
在中断函数里,调用了函数mdproc,也就是MODBUSProcessing函数。
void mdproc(uchar text)
{
        if(地址不匹配)
                {
                        地址核对();
                }
        else
                {
                        接收数据();
                        CRC核对();
                        设标志位();
                }
}

程序采用nodeok标志是否正确地址,默认0,非匹配地址。
地址核对函数如下,
                if(text==node)
                {        nodeok=1;
                        datalen=7;
                        mrx[0]=text;
                        revptr=1;
                }
将接收的SBUF和node(地址)对比,如果一致,则将nodeok置1,并且作为数组mrx[]的第一个元素,从中也可以看出采用的是RTU模式。
这里有个疑问,地址核对函数并不知道text是否是第一位,如果报文数据中有和其它设备地址一样的数据,则其它设备也可能响应。先继续,等理解完成后验证一下。

mrx[revptr++]=text;   //接收数据

                if(revptr==8)
                {        nodeok=0;
                        temp16_1=Crc16(mrx,6);
                        temp16_2=mrx[6];
                        temp16_2<<=8;
                        temp16_2|=mrx[7];
                        if(temp16_1==temp16_2)
                        {       
                                crcok=1;
                          }                               
                }
数据长度是固定的,接收完后,复位nodeok,以便下次重新确认。
然后就是CRC校验(RTU模式),如果传输没问题,
则置位crcok,表示数据正确传送完毕。

然后,再回到主函数中,

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2013-12-2 11:35:53 | 显示全部楼层
不顶对不起楼主啊!这么好的学习资料怎么能沉了!一起学习PLC!

出0入0汤圆

 楼主| 发表于 2013-12-2 12:06:25 | 显示全部楼层
本帖最后由 oldbeginner 于 2013-12-2 12:16 编辑
oldbeginner 发表于 2013-12-2 11:08
在继续主函数之前,先看一下中断函数,

在主函数里,(省掉reving,控制LED亮灭)
        rw=0;  //accroding to the MAX485 IC, /RE=1 means transfering ,and /RE=0 means receiving ,after INIT ,default state is receiving for SLAVE
        while(1)
        {
                if(crc0k==1)
                {
                        生成MODBUS报文( );
                        
                        发送报文( );
                }
        }

在循环外,rw=0,就是RS485的那根CTRL_PIN,控制发送或接收,在发送报文之前都是0。

*************************************
生成MODBUS报文( );
                        modbus[3]=ad0831read();  //get the ADC data
                        
                        for(i=0;i<9;i++)
                                {mtx=modbus;}
                        
                     
  temp16=Crc16(modbus,9);
                        mtx[9]=(temp16>>8)&0x00ff;
                        mtx[10]=temp16&0x00ff;

程序开头定义uchar modbus[9]={0x00,0x04,0x06,0x00,0x00,0x00,0x00,0x00,0x00};
直接代入,看下效果
modbus={node , 0x04 , 0x06 , ad0831read() , 0x00, 0x00 , 0x00 , 0x00, 0x00 , 0x00};

然后赋值给mtx[ ];
mtx = {node , 0x04 , 0x06 , ad0831read() , 0x00, 0x00 , 0x00 , 0x00, 0x00 , 0x00 , crc高位 , crc低位 };

功能,04 read input registers-读输入寄存器

************************************************

报文做好后,就可以发送了。
                        rw=1;
                        
                        delaycnt=0x3ff;
                        while(delaycnt--){}
                        for(i=0;i<11;i++)
                        {        temp8=mtx;
                                SendOneByte(temp8);
                        }
                        crcok=0;
                        delaycnt=0x3ff;
                        while(delaycnt--){}
                        
                        rw=0;
报文发送后,crcok和rw复位,以便下一次确认。
                        delaycnt=0x3ff;
                        while(delaycnt--){}
应该是个延时函数,确保报文发送前后有>=3.5字符时间。这个程序有点问题,在Timer0中断中是timercnt,但是并没有被其它函数使用到,所以本程序并没有利用定时器来计时,而是自减delaycnt来计时。先继续,理解完之后再修改。
原因找到了,就在仿真的图片上的提示,
原作者为了方便观察,降低了轮询频率与系统响应速度,很好。

搜了一下,http://www.amobbs.com/thread-3349665-1-1.html
静止时间和波特率有关

  例如:波特率=9600,则:一位停止位+1位校验位+8位数据位+1位起始位=11位
  也就是说1秒钟传输9600位,则1秒钟传输9600/11=872字节
  显然3.5字符静止时间

    1000ms        x
   --------   = -----    ===> x=4ms
     872       3.5

 即:静止时间=4ms

 可定义一个1ms的定时中断,当接收到一个字符后,开启定时器,并且清除计时值,当中断中“读数值”累计>=4时,表示已经接收到一帧完整的MODBUS数据。
*************************************************************

CRC和ad0831都只是直接调用,并不涉及细节,这样从设备程序完成第一遍理解。能看出,这个程序还需要修改,不过不影响目前的入门。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2013-12-3 07:39:52 | 显示全部楼层
本帖最后由 oldbeginner 于 2013-12-3 07:46 编辑
oldbeginner 发表于 2013-12-2 12:06
在主函数里,(省掉reving,控制LED亮灭)
        rw=0;  //accroding to the MAX485 IC, /RE=1 means tr ...

然后,对主设备的程序进行同样理解,首先把大文件模块化。这里曾遇到了问题,后来发现原因是原程序定义变量时没有赋值,而是在主函数中赋初值,结果移植这些变量遇到了问题。


首先看串口中断
void UARTInterrupt(void) interrupt 4
{        uchar byterev;
    if(RI)
    {
        RI = 0;
        byterev=SBUF;
        mdproc(byterev);
    }
    else
        TI = 0;
}

和从设备的串口中断一样,在中断里调用mdproc函数,
volatile void mdproc(uchar b)
{
        地址核对();
        接收数据();
        校验();
}

地址核对,和从设备程序一致,如果地址正确,则设置标志位nodeok=1,并进行赋值和revptr++
        if(nodeok==0)
        {        if(b==modbus[0])
                {        nodeok=1;
                        revptr=1;
                        mrx[0]=b;
                }
        }

接收数据,(隐含了nodeok=1的条件)
        else if(revptr<11)
        {        mrx[revptr++]=b;
        }

生成校验数据,并核对(同样隐含了nodeok=1的条件)
        else
        {
                revptr=0;
                nodeok=0;
                temp16_1=Crc16(mrx,9);
                temp16_2=mrx[9];
                temp16_2<<=8;
                temp16_2|=mrx[10];
                if(temp16_1==temp16_2)
                {
                        crcok=1;
                }                                
        }
}
***********************************

再回到主函数,所有和LCD显示相关的都会被省略,不影响理解。
void main(void)
{
        。。。。
        串口初始化();
        md处理标志位=1;
        校验标志位=1;
        485发送标志位=1;

        while(1)
        {
                if((md处理标志位=1)&&(校验标志位=1))
                        {
                                处理之前串口收到的数据();
                                显示数据处理();
                                
                                设定从机地址();

                                生成报文();

                                发送报文();

                                md处理标志位=0;
                                校验标志位=0;
                                485发送标志位=0;
                        }
        }

处理之前串口收到的数据()
                        temp16_1=mrx[3];
                        temp16_1*=temp16_2; // get the data from the communication and prepare the next frame for send
                        temp16_1>>=7;
再看一下mrx[3]的内容

mrx[3] 就是 从机里的ad0831read()返回值。

显示数据处理()
            switch(mrx[0])
            {    case 0x01:{row4[1]=(temp16_1/100)+0x30;row4[3]=((temp16_1%100)/10)+0x30;row4[4]=(temp16_1%10)+0x30;break;}
                case 0x02:{row4[6]=(temp16_1/100)+0x30;row4[8]=((temp16_1%100)/10)+0x30;row4[9]=(temp16_1%10)+0x30;break;}
                case 0x03:{row4[11]=(temp16_1/100)+0x30;row4[13]=((temp16_1%100)/10)+0x30;row4[14]=(temp16_1%10)+0x30;break;}
                default:{break;}
            }
mrx[0]就是从机里的node,地址。

设定从机地址()

            modbus[0]=(asknode++)+0x01;  // rolling the node of SLAVE for polling
            asknode%=3;
modbus[0]保存从机地址,每次+0x01,每三次循环。

生成报文()
            temp16=Crc16(modbus,6);  // generate the CRC and save it in an 16bit var
            for(i=0;i<6;i++)
                        {mtx=modbus;}
            mtx[6]=(temp16>>8)&0x00ff;
            mtx[7]=temp16&0x00ff;

发送报文()
                        for(i=0;i<8;i++)
                        {        temp8=mtx;
                                SendOneByte(temp8);
                        }
可以看出,报文长度为8位。内容如下,


*****************************************
下一步联合理解主从设备的报文。补充,
void Timer0Interrupt(void) interrupt 1
{
    TH0 = 0x3C;
    TL0 = 0x0B0;
    if(++timercnt==5)
    {    procok=1;
        timercnt=0;
    }
}主设备的定时器0是起作用的,用来设置处理标志位procok=1,每隔一段时间,主设备才运行一下,因为modbus协议规定只有主设备才能发起通讯,所以可以控制通讯频率。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2013-12-3 08:24:57 | 显示全部楼层
一直在关注楼主

出0入0汤圆

 楼主| 发表于 2013-12-3 08:36:11 | 显示全部楼层
oldbeginner 发表于 2013-12-3 07:39
然后,对主设备的程序进行同样理解,首先把大文件模块化。这里曾遇到了问题,后来发现原因是原程序定义变 ...

主设备利用Timer0中断控制发送频率,对我来说可以调节频率看清发送内容了。


整个过程是这样的,




然后,主机等待定时器0的通知,进行下一次发送。
这样主从设备的如何交互的就可以理解了。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2013-12-10 16:15:27 | 显示全部楼层
写的真详细

出0入0汤圆

发表于 2013-12-24 21:03:21 | 显示全部楼层
这个必须顶

出0入0汤圆

发表于 2014-1-7 11:08:12 | 显示全部楼层
感谢楼主详细的学习笔记!

出0入0汤圆

发表于 2014-1-7 19:33:21 | 显示全部楼层
楼主写的很详细。最好能把RS485防雷 EMI 接口讲解下。

出0入0汤圆

发表于 2014-4-13 16:26:38 | 显示全部楼层
本帖最后由 nos002 于 2014-4-13 16:44 编辑

/***************************************************************************************
* 名 称:接收中断
* 参 数:
* 返 回:
* 功 能:CSMA冲突碰撞检测,字节数据发送间隔时间写入,接收数据至公共缓冲区,数据包接收完成
*        通知接收数据处理函数取走数据。公共缓冲区长度为256字节。波特率为250K,发送或接收
         一字节约为24us,10字节约需240us,高波特率有利于减少数据碰撞的发生。
* 说 明:字节数据发送间隔等待时间由这里写入,确保发送任务函数再次发送时自身接收和对方接收中断
*        已经退出中断,这样自身或对方接收中断方可再次及时响应,否则无法正常通信。尚未添加
*        CRC校验。      
***************************************************************************************/
void __attribute__((interrupt, shadow, no_auto_psv)) _U2RXInterrupt(void)
{
    unsigned char Receive;
    static unsigned char i;
    static unsigned char LastReceive;
    static unsigned char NewDataOccur;
   
     IFS1bits.U2RXIF = 0;                          // manually cleared U2RX Interrupt flag
     Receive =U2RXREG;
     T1CONbits.TON =0;                          // 启动字符发送间隔定时器
     TMR1 =65536-DELAY_50us;              // 否则无法正常通信   
     T1CONbits.TON =1;
     if( TransStart || TransEND)                // 忽略上电或者数据开始发送时的空接收
     {
            TransEND =0;
            TransStart =0;
            MachinePowerON =0;
            return;
    }   
    NETWORKbits.NetWorkBusy=1;                   //传递消息给发送任务函数,等待接收中断退出中断以后再行发送,
    if(NETWORKbits.Transmitting)                     //监听,接收的是自身发送的数据
    {            
            T1CONbits.TON =0;                          //传递消息给发送任务函数,等待接收中断退出中断以后再行发送,
            TMR1 =65536-DELAY_50us;              //否则无法正常通信   
            T1CONbits.TON =1;   
            NETWORKbits.Transmitting =0;
            if(Receive != LastTrans)                     //接收到的监听数据和发送的数据不一致,说明网络冲突。
              StopTransmit();                  
    }
    else //接收数据至公共缓冲区,数据包接收完成通知接收数据处理函数取走数据。   
    {  
                Uart2RBuf[i++]=Receive;
                if(NewDataOccur)//检索到数据包头后,下一次中断到这里则接收到了数据包的第三字节
                {
                    if(i == (Uart2RBuf[NewDataLocation] + NewDataLocation-2))//协议第三字节为数据长度,数据包接受完以后复位接收缓冲区
                    {
                          TakeDataMSG=1;
                          NewDataOccur=0;
                          LastReceive =0;
                          i = 0;                           //复位接收缓冲区
                    }
                }
                if((Receive==0xaa)&&(LastReceive==0x55)) //检测到数据包头,可以添加modbus 6字节包头标识
                {
                      NewDataLocation = i;
                      NewDataOccur =1;
                }
                if(!(i==0))
                   LastReceive = Receive;
        
     }
}
/********************************************************************************************
*
*
*
* 功 能:串口发送数据中断
********************************************************************************************/
void __attribute__((interrupt, shadow, no_auto_psv)) _U2TXInterrupt(void)
{

        IFS1bits.U2TXIF = 0;                                        // manually cleared U2RX Interrupt flag
        UartTransEN=1;
}
/******************************************************************************************
* 名 称:
* 参 数:
* 返 回:
* 说 明:用于发送数据冲突碰撞等待,由随机函数决定延时时间;以及字节数据发送间隔等待,确保
*        再次发送时自身接收和对方接收中断已经退出中断,这样自身或对方接收中断方可再次及时响应,
*        否则无法正常通信。
*******************************************************************************************/
void __attribute__((interrupt,shadow,no_auto_psv)) _T1Interrupt(void)
{
        T1CONbits.TON =0;
        IFS0bits.T1IF =0;
        if(NETWORKbits.CollisionWait)
        {
                NETWORKbits.CollisionWait =0;
                NETWORKbits.TransStart=1;
        }
        NETWORKbits.NetWorkBusy =0;               
}

出0入0汤圆

发表于 2014-4-13 16:47:24 | 显示全部楼层
本帖最后由 nos002 于 2014-4-13 16:59 编辑

/**************************************************************************************
* 名 称:
* 参 数:
* 返 回:
* 说 明:
***************************************************************************************/
void ReceiveDataServer(unsigned char *UartReceive)
{
    unsigned char i;
    unsigned char offset;
    unsigned char Index;
    unsigned char Length;
    unsigned char DataKind;
    unsigned char Temp[50];                 //尽可能快的取走UART接收缓冲区数据
    if(TakeDataMSG)
    {        
        TakeDataMSG =0;
        Index = NewDataLocation-2;
        offset = NewDataLocation;   
        Length = UartReceive[offset];                 //通信协议第三字节为数据包长度,数据包头为0x55、0xaa,
        DataKind =UartReceive[1+offset];            //只要检测到数据包头,则数据长度字节始终是这个偏移量            
                                                                  //协议里数据长度包括数据包头:0x55,0xaa,定义数组时计算进去了
                                                                  //第四字节,数据种类
        for(i=0; i<Length; i++)                          //包括数据包头,将UART接收缓冲区的数据尽可能快取走           
           Temp = UartReceive[Index+i];            //接收的第三字节为数据包长度,第四字节为数据类型
        //分送接收的数据到目的地
        if(DataKind == PARAMETER)         
        {
            Length =(Length/2);                       //8位数据长度对应的16位数据长度
            for(i=0; i<Length; i++)                    //8位数据转16位数据,协议接收数据高8位在前低8位在后
            {
                offset =i*2;                    
                RParameterBuf = Temp[offset];       //高字节
                RParameterBuf = RParameterBuf<<8;
                RParameterBuf += Temp[1+offset];    //加上低字节
            }      
        }   
        if(DataKind == COMMAND)      
        {
            for(i=0; i<Length; i++)      
               RCommandBuf = Temp;   
        }
        AnswerBuf[4] =COMPLETE;                       //返回接收成功数据信号                        
    }                  
}               
/**************************************************************
* 名 称:
* 参 数:
* 返 回:
* 功 能:
**************************************************************/   
unsigned char Uart2Transmit(unsigned char *tArray)
{
    unsigned char i=0;
    static unsigned char TransLength;   
    static unsigned char Number;
    static unsigned char Index=0;
    if(NETWORKbits.TransStart) //发送数据冲突,等待随机延时后重新装载数据发送
    {
        Number=0;
        Index=0;  
        NETWORKbits.TransStart=0;   
    }           
    if(Number<1) //只装载一次发送数据
    {
        Number++;
        Index=0;
        TransLength =tArray[2];
        for(i=0;i<TransLength;i++)        //装载待发送数据至发送缓冲区
            Uart2TBuf=tArray;
        oUART2_DE =1;
        IFS1bits.U2TXIF =0;
        IEC1bits.U2TXIE =1;
        Index = 0;  
        //LastTrans =Uart2TBuf[0];
        U2TXREG =Uart2TBuf[Index++];
        NETWORKbits.Transmitting =1;
        U2STAbits.UTXEN =1;
        //TransStart=1;  
    }
    else if(UartTransEN)  //发送中断里置非零数,一个字节发送完毕
    {
        UartTransEN=0;
        if(TransLength>0)
        {   
            LastTrans =Uart2TBuf[Index-1];
            U2TXREG =Uart2TBuf[Index++];
                NETWORKbits.Transmitting =1;
                TransLength--;
        }
        else//数据包发送完,关闭RS485发送使能端口
        {
            Number=0;
            Index=0;
            TransEND =1;
            oUART2_DE =0;
            U2STAbits.UTXEN =0; //复位
            IEC1bits.U2TXIE =0;
            return 1;
        }
    }        
    return 0;        
}

出0入0汤圆

发表于 2014-4-13 17:02:47 | 显示全部楼层
本帖最后由 nos002 于 2014-4-13 17:20 编辑

/***************************************************************************
* 程序名:UART_RS485_CSMA_CD.c

* RS485通信程序,带冲突检测的载波侦听多路访问
* CSMA/CD(carrier sensemultiple access with collision detection)协议,

* 系统中设置定时器0作为延时计时器,他有两组延时参数。一组用于侦听网络是否空闲。
* 每次接收中断时,给定时器0重装延时参数,并设置网络忙标志。该参数的值应保证
* 在正常的数据发送时,定时器不会溢出。这样,若有节点正在发送数据,定时器0将
* 被反复重装,并设置网络忙标志。若所有节点停止发送数据,定时器将溢出,此时,
* 停止计时并清除网络忙标志,表示网络空闲。发送程序检测到该标志,即可开始发送
* 数据。发送程序每发送1 B数据时,将该数据存入一个临时变量中。此时,接收程序
* 同时也会接收到一个数据,将接收到的数据与临时变量中的数据进行比较,若相等,
* 表示数据发送成功,否则,表示发生冲突,此时立即停止发送,并给定时器0设置一
* 个随机延时值,延时结束后重复上述过程。

*****************************************************************************/
#include<p24F08KL302.h>
#include<stdlib.h>
#include"LED.h"
#include"DisplayBoard.h"
#include"485_DisplayBoard.h"

#define DELAY_50us     200
#define DELAY_2500us   20000


volatile NETWORK_BITS   NETWORKbits;
volatile R485RECEIVE    R485RX;


/*************************************************************
* 显示板发送数据包结构:
* 包头(0x55,0xaa)、 数据长度、数据类、数据、CRC_H、CRC_L
* 数据类:”参数”、”命令或传感”
* 参数:真空时间,加热时间,恒温时间,温度档位
* 命令或传感: 机器进程、停止命令,气囊开始,加热开始,恒温开始、脚踏状态、气嘴传感、电流传感

***************************************************************/
unsigned char
    UartTransEN,                     //发送允许
    TCommandBuf[20]={0x55,0xaa,13,COMMAND,1,2,3,4,5,6},  //命令位置由宏定义指出                                                                                                                              
    RCommandBuf[20],
    TParameterBuf[30],               //写入UART发送缓冲区之前不能被改写,所以独有
    Uart2TBuf[30],                   //一次数据包发送完之前不能被其他任务函数改写,只能由串口发送函数自己决定
    Uart2RBuf[256],                  //接受数据缓冲,长度可变
    AnswerBuf[5] ={3,ANSWER,0};      //应答,COMPLETE,RESEND
unsigned char
    TransEND,
    TransStart,
    LastTrans,
    Uart2State,
    MachinePowerON,
    ReceiveOccur,
    TakeDataMSG,
    NewDataLocation;
unsigned int
    RParameterBuf[10],            
    ParameterBuf[10]={0x55aa,(20<<8)+PARAMETER,1,2,3,4,5,6};//C30内建EEPROM读写函数原型参数为uint,传递uchar参数无法读写;
                                                            //UART传送char数组,传送前要转换成字节数组

/**************************************************************
* 名 称:
* 参 数:
* 返 回:
* 功 能:
***************************************************************/
void T1Init(void)
{
    TMR1=0;
    PR1 =0xffff;
    T1CONbits.TCS=0;
    T1CONbits.TCKPS =0b00;
    T1CONbits.TGATE =0;
    IPC0bits.T1IP =0b011;
    IFS0bits.T1IF =0;
    IEC0bits.T1IE =1;
   
}   
/**************************************************************
* 名 称:
* 参 数:
* 返 回:
* 功 能:
***************************************************************/
void Uart2Init()
{  
        U2BRG = 31;                            // 250K

        U2MODEbits.UARTEN = 0;                // bit15, TX, RX DISABLED, ENABLE at end of func
        U2MODEbits.USIDL = 0;                // bit13, Continue operation at Idlestate,0 = 处于空闲模式时继续工作
        U2MODEbits.IREN = 0;                // bit12, IrDA En/Decoder is disabled
        U2MODEbits.RTSMD = 0;                 // bit11, flow control mode,0 = UxRTS 处于流控制模式
        U2MODEbits.UEN = 0b00;                // bits8,9,TX,RX enabled, CTS,RTS not
        U2MODEbits.WAKE = 0;                // Bit7, No Wake up (since we don't sleep here)
        U2MODEbits.LPBACK = 0;                // bit6, Loop-back is disabled
        U2MODEbits.ABAUD = 0;                // bit5, auto baud is disabled
        U2MODEbits.RXINV = 0;                // bit4, IdleState = 1
        U2MODEbits.BRGH = 1;                // bit3, low boud rate
        U2MODEbits.PDSEL = 0b01;         // bit2,1,8bit no parity
        U2MODEbits.STSEL =1;                // bit0, one stop bit        
               

        U2STAbits.UTXISEL1 = 1;            // bit15, 01 = 当最后一次发送完成(最后一个字符移出发送移位寄存器)且所有的发送操作均完成时,产生中断
        U2STAbits.UTXISEL0 = 0;     // bit13, Other half of Bit15
        U2STAbits.UTXINV = 0;                 // bit14, IRDA config,发送奇偶校验翻转位
                                    //        IREN = 0:1 = UxTX 空闲状态为1,
                                    //                  0 = UxTX 空闲状态为0,
        U2STAbits.UTXBRK = 0;                // bit11, sync break tx is disabled,0 = 同步间隔发送被禁止或已完成
        U2STAbits.UTXEN = 0;                // bit10, transmit  is disabled
                                    
        U2STAbits.URXISEL = 0b00;        // bit7,6,interrupt flag bit is set when RXBUF is filled whith 1 character
                                    //        0x = 当接收到一个字符时,中断标志位置1
        U2STAbits.ADDEN = 0;                // bit5,  address detect mode is disabled,0 = 地址检测模式禁止
        
        IFS1bits.U2RXIF = 0;                // clear interrupt flag of rx
        IEC1bits.U2RXIE = 1;                // enable rx recieved data interrupt
        IFS1bits.U2TXIF = 0;                // clear interrupt flag of rx
        IEC1bits.U2TXIE = 0;                // enable rx recieved data interrupt
        
        IPC7bits.U2TXIP=3;
        IPC7bits.U2RXIP=5;
        

        U2MODEbits.UARTEN = 1;                // UART2 is Enabled
    U2STAbits.UTXEN = 0;                // bit10,transmit  is enabled
   
    oUART2_RE=1;                // 上机时,禁止接收、发送
    Nop();
    Nop();
    oUART2_DE=0;
    Uart2State =FREE;
    MachinePowerON= 1;          // 上电串口空接收,给出上电信号以便串口接收中断处理
}
   
/**************************************************************
* 名 称: StopTransmit
* 参 数:
* 返 回:
* 说 明:获取随机延时数
***************************************************************/
void StopTransmit(void)
{
        unsigned int iTime;
        U2STAbits.UTXEN =0;
        IEC1bits.U2TXIE =1;
        iTime =rand()/3 + DELAY_2500us;       //随机延时
        T1CONbits.TON =0;
        TMR1 =65536-iTime;
        T1CONbits.TON =1;
        NETWORKbits.CollisionWait =1;      //网络冲突等待
}
/**************************************************************/
unsigned char HighByte(unsigned int variable)
{
    unsigned char temp;
    temp = (unsigned char)((variable&0xff00) >>8);
    return temp;
}
/**************************************************************/
unsigned char LowByte(unsigned int variable)
{
    unsigned char temp;
    temp =(unsigned char)(variable &0x00ff);
    return temp;
}        
/**************************************************************
* 名 称:
* 参 数:
* 返 回:
* 说 明:整形数组转换成高低字节,并存入字节数组,高字节在前,低字节在后;
***************************************************************/
void ConvertIntToByteArray(unsigned char *Target, unsigned int *Source)
{
    volatile unsigned char i,j,size;
    size = HighByte(Source[1])/2;
    for(i=0;i<size;i++)
    {
        j = i*2;
        Target[j] = HighByte(Source);
        Target[1+j] = LowByte(Source);
    }  
   
}
/**************************************************************
* 名 称:
* 参 数:
* 返 回:
* 说 明:
***************************************************************/
void PowerONSync(void)
{
    static unsigned int i;
    if(i==400)
    {
        ConvertIntToByteArray(TParameterBuf,ParameterBuf);   
        ParameterTransRQ =1;                         //上机稳定后,为使显示板和驱动板参数同步,显示板将发送一次参数数据
    }
    if(i>50)
      oUART2_RE =0;
    if(i<410)
      i++;
}   
/**************************************************************
* 名 称:
* 参 数:
* 返 回:
* 功 能:清空待发送的命令或参数缓冲区,除固定的数据长度和数据类型字节外,
*        以便更新待发送数据。
**************************************************************/
void ClearBuffer(unsigned char *ptrBuf)
{
    unsigned char i;
    for(i=0;i<(ptrBuf[2]-4);i++)
    {
        ptrBuf[4+i] =0;
    }
}  

出0入0汤圆

发表于 2014-5-25 21:45:15 | 显示全部楼层
写的很好,开始学习,做个记号!

出0入50汤圆

发表于 2014-6-19 12:29:05 | 显示全部楼层
楼主,如果在C51里,有一个函数,会在中断和主程序里都与可能被调用,那么直接在定义这个函数时,使用reentrant做关键字后缀就可以实现,这样中断里调用此函数时,会自动加入相关的寄存器等现场包含处理,不过会增加RAM占用,尽量在这类函数里少使用变量。

出0入0汤圆

发表于 2014-7-8 11:18:50 | 显示全部楼层
oldbeginner 发表于 2013-12-2 10:15
先把主函数简化一下,这里要解决RS485多机的地址问题。

在多机通信中,SM2会根据需要随时对他进行置1或清零来完成轮询

出0入0汤圆

发表于 2014-7-12 19:54:02 | 显示全部楼层
非常好哇!谢谢!

出0入0汤圆

发表于 2014-8-21 08:26:49 | 显示全部楼层
mark,开源PLC

出0入0汤圆

发表于 2014-9-21 14:16:45 | 显示全部楼层
楼主厉害,收藏一下,方便学习

出0入0汤圆

发表于 2014-9-28 17:54:34 | 显示全部楼层

楼主厉害,收藏一下,方便学习

出0入0汤圆

发表于 2014-10-16 21:36:25 | 显示全部楼层
好东西啊,顶一下先...

出0入0汤圆

发表于 2014-10-20 18:43:53 | 显示全部楼层
厉害 学习了! 楼主威武!!

出0入0汤圆

发表于 2014-10-25 22:05:58 | 显示全部楼层
学习一下                              

出0入0汤圆

发表于 2014-10-29 21:06:32 | 显示全部楼层
膜拜+佩服   向楼主学习

出0入0汤圆

发表于 2014-11-11 15:39:57 | 显示全部楼层
非常不错的学习资料!给楼至支持一下,谢谢分享

出0入0汤圆

发表于 2014-11-15 17:30:59 | 显示全部楼层
这个确实不错哦。很详细

出0入0汤圆

发表于 2014-11-16 18:36:14 | 显示全部楼层
希望国产PLC能成长起来。支持楼主

出0入0汤圆

发表于 2015-1-12 13:26:32 | 显示全部楼层
学习modbus 很详细

出0入0汤圆

发表于 2015-1-29 19:50:03 | 显示全部楼层
xx学习。。

出0入0汤圆

发表于 2015-2-12 14:20:58 | 显示全部楼层
建议大家看看FreeModbus开源例程,本论坛里面有的!http://www.amobbs.com/forum.php? ... ighlight=FreeModbus

出85入85汤圆

发表于 2015-10-11 23:06:20 | 显示全部楼层
楼主的帖子是modbus中的精品、极品。

出0入0汤圆

发表于 2017-3-21 12:02:42 | 显示全部楼层
确实是好贴!

出0入0汤圆

发表于 2017-3-21 14:25:21 | 显示全部楼层
收藏了PLC笔记
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-5-10 20:46

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

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