您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 管理学资料 > freeModbus代码解读及移植笔记
1.FreeModbus协议分析协议必须首先调用初始化功能eMBinit()函数。后调用eMBEnable(),最后,在循环体或者单独一个任务中调用eMBPoll()函数。2.应用层协议2.1.系统的启动2.1.1.eMBInit()函数的源码分析以RTU方式为例,首先,检查调用的地址是否合法。如不合法,返回错误。如果合法则继续执行,首先,针对RTU方式还是ASCII方式,选择不同的编译模块。对需要调用的函数指针进行复制。如果移植需要改变其他用途,则要修改相应的指针,包括如下赋值:pvMBFrameStartCur=eMBRTUStart;pvMBFrameStopCur=eMBRTUStop;peMBFrameSendCur=eMBRTUSend;peMBFrameReceiveCur=eMBRTUReceive;pvMBFrameCloseCur=MB_PORT_HAS_CLOSE?vMBPortClose:NULL;pxMBFrameCBByteReceived=xMBRTUReceiveFSM;pxMBFrameCBTransmitterEmpty=xMBRTUTransmitFSM;pxMBPortCBTimerExpired=xMBRTUTimerT35Expired;然后调用eStatus=eMBRTUInit(ucMBAddress,ucPort,ulBaudRate,eParity);具体初始化通讯端口。2.1.2.eMBRTUIniteMBRTUInit这个函数主要干两件事:第一,初始化串口:if(xMBPortSerialInit(ucPort,ulBaudRate,8,eParity)!=TRUE){eStatus=MB_EPORTERR;}这个函数在portserial.c中,需要用户在移植的时候根据自己的处理器编写。第二,初始化计时器:首先要根据波特率计算一下是3.5~5.0个字节周期的时间,然后再调用xMBPortTimersInit((USHORT)usTimerT35_50us),初始化计时器。这个函数在porttimer.c中,需要用户在移植的时候根据自己的处理器编写。2.1.3.eMBEnable源码分析首先,看看Modbus功能是否是被关闭的,如果不是被关闭(可能是没有被初始化或者已经打开),就返回错误。如果是disable状态,就干下面两件事:l调用pvMBFrameStartCur()。由于这是个函数指针,在模块eMBInit中,指向了eMBRTUStart函数n在源代码中有这样一段注释:,意思是,首先设置成STATE_RX_INIT,然后打开计时器,等待t3.5以后,进入STATE_RX_IDLE状态。n看源代码中,首先有设置Receiver的状态,后调用vMBPortSerialEnable,设置接收状态,然后打开定时器。n当定时器中断后,自动调用中断服务程序,在中断服务程序中,只调用了pxMBPortCBTimerExpired,而这是一个函数指针,在RTU方式初始化时,被指向了xMBRTUTimerT35Expired()函数。nxMBRTUTimerT35Expired函数在mbrtu.c中,在这里,我们只看第一种方式,就是进入初始化状态,在t35时间以后,只调用了一个xNeedPoll=xMBPortEventPost(EV_READY);nxMBPortEventPost函数就是在事件队列里加了一个EV_RDY事件。l然后,将eMB状态改为使能状态,l初始化结束。2.2.总线侦听eMBPoll()首先,判断系统是否被使能,如果没有,则返回错误值。然后,检查是否有事件发生,如果有,则根据不同类型的事件响应:l如果是EV_RDY,表示系统刚刚进入侦听状态,则什么都不做;l如果状态为EV_FRAME_RECEIVED,也就是接收到完整的帧,做下面两件事情:n调用eStatus=peMBFrameReceiveCur(&ucRcvAddress,&ucMBFrame,&usLength)。这是一个函数指针,在eMBInit中,被初始化指向eMBRTUReceive。neMBRTUReceive这个函数首先校验帧的长度和CRC,然后从协议中解析出地址、数据和长度。n然后检查地址,如果是广播地址或者是本机地址,就调用xMBPortEventPost(EV-EXECUTE),将接收器的状态更改为EV_EXECUTE。l如果状态为EV_EXECUTE,就在函数列表中检查,有没有与命令字段相符合的函数来解析相应则执行该函数,否则返回非法功能代码。2.3.数据发送发送数据通过指针eMBRTUSend,调用eMBRTUSend函数。2.3.1.eMBRTUSend函数这个函数的作用就是打包,将数据打包成帧。l首先,检查接收状态。因为MODBUS是基于RS-485半双工通讯,所以当正在接收数据时,不发送该帧。l如果总线空,就将数据打包,将地址和CRC加入数据帧l将总线状态改为发送。2.4.功能注册l对于指定的功能代码,需要一个功能回调函数来处理,格式如下。eMBExceptioneMXXXXXX(UCHAR*pucFrame,USHORT*usLen)l需要通过函数eMBRegisterCB(功能代码,函数名)加到处理代码中。具体源码分析从略。2.4.1.prvvUARTTxReadyISR()总线状态改为发送后,会在发送缓冲时,自动调用prvvUARTTxReadyISR()中断服务程序。prvvUARTTxReadyISR()只调用了一个函数,就是pxMBFrameCBTransmitterEmpty()。2.4.2.pxMBFrameCBByteReceived()pxMBFrameCBTransmitterEmpty()是一个指针,指向了xMBRTUTransmitFSM函数。3.数据链路层协议数据链路层是最基本的打包部分,将数据打包成帧,送到应用层。在数据链路层协议中,使用中断方式来接受。那么每次接收到字符就自动调用接收字符的ISR程序。按照规定,应该将中断服务程序安装给prvvUARTRxISR(void)函数。实际上这个函数只调用了一个函数:pxMBFrameCBByteReceived(),这个指针调用了xMBRTUReceiveFSM函数。3.1.xMBRTUReceiveFSM()函数函数首先检查是不是处于发送状态。如果处于发送状态,直接退出。l首先调用xMBPortSerialGetByte((CHAR*)&ucByte),获取从串口读到的字符。l然后检查接受状态:n如果是错误状态或者处于初始化状态,那么直接等待,错过该帧。n如果是STATE_RX_IDLE空闲状态,则将指针重置,将收到的第一个字节存储到缓冲区,并将状态改为STATE_RX_RCV状态。n如果处于接收状态,就判断,如果缓冲区未满,就将收到的字节放入缓冲区,否则改为错误状态。l不管在任何状态,最后都开启了t35计时器。在t35结束的时候,通过指针调用了xMBRTUTimerT35Expired()函数。lxMBRTUTimerT35Expired()函数检查状态,如果是接收状态那就表明,已经有t35这么长的时间里,没有收到任新字节,当前的帧结束。在队列里增加一个EV_FRAME_RECEIVED事件。l如果是错误状态,什么都不做。l然后关掉计时器,将状态改为空闲。3.2.xMBRTUTransmitFSM()函数xMBRTUTransmitFSM首先判断总线是否忙,如果忙,则终止。如果不忙,则继续,根据发送状态变量:l如果当前为STATE_TX_IDLE(空闲)状态,则打开端口发送l如果当前状态为STATE_TX_XMIT,则进一步判断发送队列是否为空,n如果不空,则发送下一个字符n如果空,说明发送完成,关闭发送端口,改为侦听,并将状态改为空闲。4.传输控制除了传输控制以外,还有传输控制的若干函数。通过下面几个指针来调用:pvMBFrameStopCur()pvMBFrameCloseCur()4.1.pvMBFrameStopCur()函数pvMBFrameStopCur是一个函数指针,在RTU方式下,它指向eMBRTUStop()函数。该函数做下面几件事情:l关闭侦听和发送freeModbus的代码库还是很好用的,本人在wince和C8051F410下均移植成功(只用到RTU模式)。但freeModbus提供的文档比较少,只能对照着Modbus协议一点点试着读懂源代码。下面是阅读代码期间的跟踪笔记:1、eMBErrorCode为枚举类型变量,代表错误码,共有8个错误代号。常用的是MB_ENOERR,即没有错误。2、eMBMode枚举类型变量代表设备的工作模式,分别是MB_RTU、MB_ASCII和MB_TCP。3、eMBEventType枚举类型变量定义了event的类型,分别是EV_READY,代表Startup启动完成;EV_FRAME_RECEIVED代表接收到帧;EV_EXECUTE代表执行功能函数;EV_FRAME_SENT代表帧已发送。4、eMBParity枚举类型变量代表奇偶校验选项,分别是MB_PAR_NONE无校验,MB_PAR_ODD奇校验,和MB_PAR_EVEN偶校验。5、mb.c文件中的静态变量ucMBAddress存储设备地址,此变量在eMBInit函数中初始化。6、在C51Modbus中将freeModbus库中的源码进行了更改,例如尽量不使用函数指针,而是直接调用相关功能函数,根据eMBCurrentMode中的工作模式,来判断调用哪个函数。在freeModbus库中某些函数声明前加上reentrant,这是Keil编译器特有的关键词。这样做带来的一个不足是:不能动态绑定函数,从而导致库代码失去可移植性。这样做是C51编译器与ANSI标准不兼容的特殊性导致的。7、ENTER_CRITICAL_SECTION()和EXIT_CRITICAL_SECTION()宏,实际上就是关闭和打开全局中断。8、带xMBPort前缀的函数都属于portlayer层,也就是独立于ModBus协议栈。9、freeModbus库中函数名称的第一个字母表示返回值类型,例如e表示返回enum枚举类型;v表示void无返回值;x表示BOOL布尔类型。注意这条规则并不是总成立,但主要函数基本上还是符合此规则的。第一个字母后的MB代表是属于ModBus协议栈的函数。10、port.h文件中宏#defineF_MCU定义了单片机的工作频率。需要用其值计算Uart0定时器和Tick定时器的重装入值。11、在程序主函数main中,使用协议栈的方法是:eStatus=eMBInit(MB_RTU,0x0A,0,9600,MB_PAR_EVEN);/*EnabletheModbusProtocolStack.*/eStatus=eMBEnable();for(;;){(void)eMBPoll();……}12、在portlayer层的xMBPortSerialInit函数中,需要根据传入的波特率、奇偶校验、数据位长度设置来配置Uart0及其使用的定时器。13、在portlayer层的vMBPortSerialEnable函数中配置接收和发送使能,由于在单片机的寄存器SCON0中只有接收使能控制位REN0,而没有发送使能控制位,所以在portserial.c文件中又定义了一个TxEnable变量,用来表示发送的使能状态。若同时关闭接收和发送,则要关闭Uart0中断,即让ES0=0。14、eMBRTUInit函数中的变量usTimerT35_50us代表如果50us进行一次Tick的话,T35超时的Tick次数。这个公式很重要:usTimerT35_50us=(7UL*220000UL)/(2UL*ulBaudRate);函数xMBPortTimersInit要以变量usTimerT35_50us为传
本文标题:freeModbus代码解读及移植笔记
链接地址:https://www.777doc.com/doc-5193571 .html