开源PLC学习笔记14(MODBUS 入门)——2013_12_01
前13节笔记都是分析的单机工作的仿三菱PLC,只实现了一个简单的功能,即使后面使用STM32把开源PLC的功能都加上,也是一个没有竞争力的简单产品。而重要的原因就是缺少MODBUS,可以让很多PLC联机工作的协议。了解了一下MODBUS协议(第一阶段不含TCP),感觉和学习三菱PLC通讯协议有点像,决定用同样的方法,找代码边理解边学习,并给上节的程序加入modbus协议,能让该程序和组态王或者Eview触摸屏通讯。
选择入门MODBUS,除了该协议真得很重要外,另外一个更重要的原因是入门资料还算丰富。
一些基本概念如下图,
然后,网上找到仿真,感受一下就可以了。
下一步就要选择入门的程序了,
http://www.amobbs.com/forum.php?mod=viewthread&tid=4546355&highlight=modbus
这个可以用PROTEUS仿真,比较方便,下一步就是分析这个例子。
要理解上面的程序,对我来说,还需要以下几个方面的知识:
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/CuriosityWzk/archive/2011/12/25/2301090.html
这个问题必须注意,可能引起程序冲突,假设你用于自动化领域,则可能导致信号产生尖峰。 产生这一警告的一个根源是:你在主循环 里调用了一个函数(如aaa),而在中断服务函数里,你用调用了这个函数(如aaa)。这样当主循环运行到该函数中 是,一旦产生中断,则在中断里又再次调用该函数!这时,很可能出错! 避免这种情况的方法很多:如,在进中断的时候置需调用该函数的标志,而在主循环中调用该函数。
开源PLC的FX1NProcessing函数并没有放在中断函数里,而是采用了上面文章推荐的方法。理解这个MODBUS例子后,再修改这个程序。
准备工作差不多了,就可以开始理解从设备的程序了,先选从设备因为感觉从设备程序简短些。
很好,顶一下 本帖最后由 oldbeginner 于 2013-12-2 10:24 编辑
oldbeginner 发表于 2013-12-2 08:34
要理解上面的程序,对我来说,还需要以下几个方面的知识:
1、RS485工作原理,包括收发信息和地址确定。
2 ...先把主函数简化一下,这里要解决RS485多机的地址问题。
void main(void)
{
局部变量定义。。。
变量赋值。。。。
node=!HighNode;
node<<=1;
node|=!LowNode;
modbus=node;
InitUART();
。。。。
while(1)
{
。。。。。
}
}
这个例子采用了硬件决定地址的方式,
分析从设备2,
node 是uchar类型,记为0000 0000
node=! P3^3; 变为0000 0001
node<<=1; 变为0000 0010
node|=!P3^2; 变为0000 0010
因为地址采用硬件识别,从设备不需要在软件里设置地址,可以使用同一个程序,对接非常多从机的系统来说可以减少误操作。
*******************************************
然后就是串口初始化,
void InitUART(void)
{
TMOD = 0x20;
SCON = 0x50;
TH1 = 0xFE;
TL1 = TH1;
PCON = 0x00;
EA = 1;
ES = 1;
TR1 = 1;
TMOD |= 0x01;
TH0 = 0x3C;
TL0 = 0x0B0;// timer sets at 20ms
ET0 = 1;
TR0 = 1;
}有疑惑的是SCON
这里SCON还是0x50,
这里并没有选择SM2=1,多机通信,什么原因?
网上暂时没有找到合适的解释,感觉是SM2=1自动找地址,而本程序MODBUS报文中含有地址,不需要SCON来找地址。网上看了一下,modbus通讯基本上SCON=0x50。
oldbeginner 发表于 2013-12-2 10:15
先把主函数简化一下,这里要解决RS485多机的地址问题。
在继续主函数之前,先看一下中断函数,
void UARTInterrupt(void) interrupt 4
{
if(RI)
{
RI = 0;
mdproc(SBUF);
}
else
TI = 0;
}
**********************************************
在中断函数里,调用了函数mdproc,也就是MODBUSProcessing函数。
void mdproc(uchar text)
{
if(地址不匹配)
{
地址核对();
}
else
{
接收数据();
CRC核对();
设标志位();
}
}
程序采用nodeok标志是否正确地址,默认0,非匹配地址。
地址核对函数如下,
if(text==node)
{ nodeok=1;
datalen=7;
mrx=text;
revptr=1;
}
将接收的SBUF和node(地址)对比,如果一致,则将nodeok置1,并且作为数组mrx[]的第一个元素,从中也可以看出采用的是RTU模式。
这里有个疑问,地址核对函数并不知道text是否是第一位,如果报文数据中有和其它设备地址一样的数据,则其它设备也可能响应。先继续,等理解完成后验证一下。
mrx=text; //接收数据
if(revptr==8)
{ nodeok=0;
temp16_1=Crc16(mrx,6);
temp16_2=mrx;
temp16_2<<=8;
temp16_2|=mrx;
if(temp16_1==temp16_2)
{
crcok=1;
}
}
数据长度是固定的,接收完后,复位nodeok,以便下次重新确认。
然后就是CRC校验(RTU模式),如果传输没问题,
则置位crcok,表示数据正确传送完毕。
然后,再回到主函数中, 不顶对不起楼主啊!这么好的学习资料怎么能沉了!一起学习PLC! 本帖最后由 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=ad0831read();//get the ADC data
for(i=0;i<9;i++)
{mtx=modbus;}
temp16=Crc16(modbus,9);
mtx=(temp16>>8)&0x00ff;
mtx=temp16&0x00ff;
程序开头定义uchar modbus={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都只是直接调用,并不涉及细节,这样从设备程序完成第一遍理解。能看出,这个程序还需要修改,不过不影响目前的入门。 本帖最后由 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)
{ nodeok=1;
revptr=1;
mrx=b;
}
}
接收数据,(隐含了nodeok=1的条件)
else if(revptr<11)
{ mrx=b;
}
生成校验数据,并核对(同样隐含了nodeok=1的条件)
else
{
revptr=0;
nodeok=0;
temp16_1=Crc16(mrx,9);
temp16_2=mrx;
temp16_2<<=8;
temp16_2|=mrx;
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;
temp16_1*=temp16_2; // get the data from the communication and prepare the next frame for send
temp16_1>>=7;
再看一下mrx的内容
mrx 就是 从机里的ad0831read()返回值。
显示数据处理();
switch(mrx)
{ case 0x01:{row4=(temp16_1/100)+0x30;row4=((temp16_1%100)/10)+0x30;row4=(temp16_1%10)+0x30;break;}
case 0x02:{row4=(temp16_1/100)+0x30;row4=((temp16_1%100)/10)+0x30;row4=(temp16_1%10)+0x30;break;}
case 0x03:{row4=(temp16_1/100)+0x30;row4=((temp16_1%100)/10)+0x30;row4=(temp16_1%10)+0x30;break;}
default:{break;}
}
mrx就是从机里的node,地址。
设定从机地址()
modbus=(asknode++)+0x01;// rolling the node of SLAVE for polling
asknode%=3;
modbus保存从机地址,每次+0x01,每三次循环。
生成报文()
temp16=Crc16(modbus,6);// generate the CRC and save it in an 16bit var
for(i=0;i<6;i++)
{mtx=modbus;}
mtx=(temp16>>8)&0x00ff;
mtx=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协议规定只有主设备才能发起通讯,所以可以控制通讯频率。 一直在关注楼主 oldbeginner 发表于 2013-12-3 07:39
然后,对主设备的程序进行同样理解,首先把大文件模块化。这里曾遇到了问题,后来发现原因是原程序定义变 ...
主设备利用Timer0中断控制发送频率,对我来说可以调节频率看清发送内容了。
整个过程是这样的,
然后,主机等待定时器0的通知,进行下一次发送。
这样主从设备的如何交互的就可以理解了。
写的真详细 这个必须顶 感谢楼主详细的学习笔记! 楼主写的很详细。最好能把RS485防雷 EMI 接口讲解下。 本帖最后由 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=Receive;
if(NewDataOccur)//检索到数据包头后,下一次中断到这里则接收到了数据包的第三字节
{
if(i == (Uart2RBuf + 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;
} 本帖最后由 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; //尽可能快的取走UART接收缓冲区数据
if(TakeDataMSG)
{
TakeDataMSG =0;
Index = NewDataLocation-2;
offset = NewDataLocation;
Length = UartReceive; //通信协议第三字节为数据包长度,数据包头为0x55、0xaa,
DataKind =UartReceive; //只要检测到数据包头,则数据长度字节始终是这个偏移量
//协议里数据长度包括数据包头:0x55,0xaa,定义数组时计算进去了
//第四字节,数据种类
for(i=0; i<Length; i++) //包括数据包头,将UART接收缓冲区的数据尽可能快取走
Temp = UartReceive; //接收的第三字节为数据包长度,第四字节为数据类型
//分送接收的数据到目的地
if(DataKind == PARAMETER)
{
Length =(Length/2); //8位数据长度对应的16位数据长度
for(i=0; i<Length; i++) //8位数据转16位数据,协议接收数据高8位在前低8位在后
{
offset =i*2;
RParameterBuf = Temp; //高字节
RParameterBuf = RParameterBuf<<8;
RParameterBuf += Temp; //加上低字节
}
}
if(DataKind == COMMAND)
{
for(i=0; i<Length; i++)
RCommandBuf = Temp;
}
AnswerBuf =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;
for(i=0;i<TransLength;i++) //装载待发送数据至发送缓冲区
Uart2TBuf=tArray;
oUART2_DE =1;
IFS1bits.U2TXIF =0;
IEC1bits.U2TXIE =1;
Index = 0;
//LastTrans =Uart2TBuf;
U2TXREG =Uart2TBuf;
NETWORKbits.Transmitting =1;
U2STAbits.UTXEN =1;
//TransStart=1;
}
else if(UartTransEN)//发送中断里置非零数,一个字节发送完毕
{
UartTransEN=0;
if(TransLength>0)
{
LastTrans =Uart2TBuf;
U2TXREG =Uart2TBuf;
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;
} 本帖最后由 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={0x55,0xaa,13,COMMAND,1,2,3,4,5,6},//命令位置由宏定义指出
RCommandBuf,
TParameterBuf, //写入UART发送缓冲区之前不能被改写,所以独有
Uart2TBuf, //一次数据包发送完之前不能被其他任务函数改写,只能由串口发送函数自己决定
Uart2RBuf, //接受数据缓冲,长度可变
AnswerBuf ={3,ANSWER,0}; //应答,COMPLETE,RESEND
unsigned char
TransEND,
TransStart,
LastTrans,
Uart2State,
MachinePowerON,
ReceiveOccur,
TakeDataMSG,
NewDataLocation;
unsigned int
RParameterBuf,
ParameterBuf={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, transmitis 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,transmitis 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)/2;
for(i=0;i<size;i++)
{
j = i*2;
Target = HighByte(Source);
Target = 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-4);i++)
{
ptrBuf =0;
}
} 写的很好,开始学习,做个记号! 楼主,如果在C51里,有一个函数,会在中断和主程序里都与可能被调用,那么直接在定义这个函数时,使用reentrant做关键字后缀就可以实现,这样中断里调用此函数时,会自动加入相关的寄存器等现场包含处理,不过会增加RAM占用,尽量在这类函数里少使用变量。 oldbeginner 发表于 2013-12-2 10:15
先把主函数简化一下,这里要解决RS485多机的地址问题。
在多机通信中,SM2会根据需要随时对他进行置1或清零来完成轮询 非常好哇!谢谢! mark,开源PLC 楼主厉害,收藏一下,方便学习
楼主厉害,收藏一下,方便学习 好东西啊,顶一下先... 厉害 学习了! 楼主威武!! 学习一下 膜拜+佩服 向楼主学习 非常不错的学习资料!给楼至支持一下,谢谢分享 这个确实不错哦。很详细 希望国产PLC能成长起来。支持楼主 学习modbus 很详细 xx学习。。 建议大家看看FreeModbus开源例程,本论坛里面有的!http://www.amobbs.com/forum.php?mod=viewthread&tid=5556510&highlight=FreeModbus 楼主的帖子是modbus中的精品、极品。 确实是好贴!{:smile:} 收藏了PLC笔记
页:
[1]