freeModbus代码解读及移植笔记(可编辑修改word版)
更新时间:2023-04-08 17:05:01 阅读量: 实用文档 文档下载
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. eMBRTUInit
eMBRTUInit 这个函数主要干两件事:
第一,初始化串口:
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()函数。
n xMBRTUTimerT35Expired 函数在mbrtu.c 中,在这里,我们只看第一种方式,就是进入初始化状态,在t35 时间以后,只调用了一个xNeedPoll =
xMBPortEventPost( EV_READY );
n xMBPortEventPost 函数就是在事件队列里加了一个EV_RDY 事件。
l 然后,将eMB 状态改为使能状态,
l 初始化结束。
2.2. 总线侦听eMBPoll()
首先,判断系统是否被使能,如果没有,则返回错误值。
然后,检查是否有事件发生,如果有,则根据不同类型的事件响应:
l 如果是EV_RDY,表示系统刚刚进入侦听状态,则什么都不做;
l 如果状态为EV_FRAME_RECEIVED,也就是接收到完整的帧,做下面两件事情:
n 调用eStatus=peMBFrameReceiveCur( &ucRcvAddress,&ucMBFrame, &usLength)。这是一个函数指针,在eMBInit 中,被初始化指向eMBRTUReceive。
n eMBRTUReceive 这个函数首先校验帧的长度和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 对于指定的功能代码,需要一个功能回调函数来处理,格式如下。
eMBException eMXXXXXX ( 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()函数。
l xMBRTUTimerT35Expired()函数检查状态,如果是接收状态那就表明,已经有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 前缀的函数都属于port layer 层,也就是独立于ModBus 协议栈。
9、freeModbus 库中函数名称的第一个字母表示返回值类型,例如e 表示返回enum 枚举类型;v 表示void 无返回值;x 表示BOOL 布尔类型。注意这条规则并不是总成立,
但主要函数基本上还是符合此规则的。第一个字母后的MB 代表是属于ModBus 协议栈的函数。
10、port.h 文件中宏#define F_MCU 定义了单片机的工作频率。需要用其值计算Uart0 定时器和Tick 定时器的重装入值。
11、在程序主函数main 中,使用协议栈的方法是:
eStatus = eMBInit( MB_RTU, 0x0A, 0, 9600, MB_PAR_EVEN );
/* Enable the Modbus Protocol Stack. */
eStatus = eMBEnable( );
for( ;; )
{
( void )eMBPoll( );
……
}
12、在port layer 层的xMBPortSerialInit 函数中,需要根据传入的波特率、奇偶校验、数据位长度设置来配置Uart0 及其使用的定时器。
13、在port layer 层的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 为传入参数,对T35 超时定时器进行设置。
15、在mbrtu.c 文件中定义了两个状态变量,一个是接收状态变量eRcvState,为eMBRcvState 枚举类型,有4 个状态,在使能ModBus 协议栈后赋予STATE_RX_INIT,即初始状态;另一个是发送状态变量eSndState,为eMBSndState 枚举类型,有两个状态,初始化为发送idle 状态,即STATE_TX_IDLE。
16、mb.c 文件中的eMBState 状态变量为枚举类型,代表设备的工作状态,有3 种状态,分别是“未初始化”、“使能”和“禁止”状态。调用完eMBInit 函数后要调用eMBEnable 函数来使能ModBus 协议栈,在其中将eMBState 状态变量从“未初始化状态”变为“使能状态”,然后使能串口和打开T35 定时器。
17、如果T35 定时器超时并产生中断,则要调用xMBRTUTimerT35Expired 函数,其内部是一个状态机转换的switch,根据当前接收状态来通过xMBPortEventPost 发送事件通知,然后关闭T35 定时器,并将当前接收状态设置为STATE_RX_IDLE。
18、eMBException 枚举型变量表示Exception 的类型,共有10 种Exception,在ModBus 协议中有定义。
19、在eMBPoll( )中,首先通过xMBPortEventGet 函数取event,如果没有则退出,若有event 的话便根据event 类型进行相应处理。EV_READY 是在协议栈初始化后xMBRTUTimerT35Expired 函数发出来的,表示startup 完成;EV_FRAME_RECEIVED
是xMBRTUTimerT35Expired 函数在T35 超时后发出的,表示已经收到了一帧,需要进行成帧处理,调用eMBRTUReceive 函数;EV_EXECUTE 是在处理EV_FRAME_RECEIVED 过程中最后一步,如果此帧的地址符合本机地址,则发出EV_EXECUTE 事件,进行应用层的处理。
20、在eMBRTUReceive 函数中首先查看帧大小是否符合要求,然后进行CRC 校验。此函数的原型是:
eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
第一个参数是为了返回帧中的地址,也就是帧中第一个字节;第二个传入的参数以后要
当做数组来使用,所以用了指针的指针类型;第三个参数表示PDU 的长度,也就是帧中除去地址字节和CRC 校验字节后的长度。
21、在eMBPoll( )中处理EV_EXECUTE 事件,首先从PDU 中提取出FunctionCode,然后根据FunctionCode 找到相应的处理函数。xMBFunctionHandler 结构体类型变量xFuncHandlers 中定义了各个FunctionCode 对应的处理函数pxHandler,函数的第一个参数ucMBFrame 是PDU 的存储地址,第二个参数usLength 返回PDU 的长度。如果帧不是一个广播帧,则需要设备发出一个回复,如果前面有错误发生,则要回复一个错误报告帧。
22、在Keil 中程序需要使用大模式编译,否则会出现error c249: 'data': segment too large 的错误。
23、若使用波特率为9600,则t3.5= ( 11 * 3.5 ) / 9600 = 4.01 ms。不能使用8 位模式的Timer,因为11.0590MHz 主频在最大48 分频后,最长的超时时间为1.11ms,不能满足T35 的超时要求。
24、freeModbus 1.5 库,在使用过程中发现了一个bug,即如果在PDU 中发送的寄存器数据长度与要读写的寄存器的数量不符,只要CRC 校验正确,freeModbus 便不会检测出来。例如写多个寄存器命令中,标明写寄存器的数量为2,也就是后面接的数据长度为4,但随后的数据只为2 个字节,即一个寄存器的数据,freeModbus 不会发现此错误,同时会将后面的CRC 校验值认作是写第二个寄存器的数据。这个bug 实际上是通过Modbus 调试精灵1.024 的一个写多寄存器bug 发现的。
正在阅读:
freeModbus代码解读及移植笔记(可编辑修改word版)04-08
忍住不说我爱你读后感10篇12-12
2019届河南省许昌平顶山两市高三第一次联合考试文综地理试卷(含答案)01-08
最新-小学语文六年级下册第二单元教学设计04-19
真宗子平讲解八字04-09
Exercises07-10
GE系统PLC硬件配置07-07
- 教学能力大赛决赛获奖-教学实施报告-(完整图文版)
- 互联网+数据中心行业分析报告
- 2017上海杨浦区高三一模数学试题及答案
- 招商部差旅接待管理制度(4-25)
- 学生游玩安全注意事项
- 学生信息管理系统(文档模板供参考)
- 叉车门架有限元分析及系统设计
- 2014帮助残疾人志愿者服务情况记录
- 叶绿体中色素的提取和分离实验
- 中国食物成分表2020年最新权威完整改进版
- 推动国土资源领域生态文明建设
- 给水管道冲洗和消毒记录
- 计算机软件专业自我评价
- 高中数学必修1-5知识点归纳
- 2018-2022年中国第五代移动通信技术(5G)产业深度分析及发展前景研究报告发展趋势(目录)
- 生产车间巡查制度
- 2018版中国光热发电行业深度研究报告目录
- (通用)2019年中考数学总复习 第一章 第四节 数的开方与二次根式课件
- 2017_2018学年高中语文第二单元第4课说数课件粤教版
- 上市新药Lumateperone(卢美哌隆)合成检索总结报告
- 移植
- freeModbus
- 解读
- 编辑
- 修改
- 代码
- 笔记
- word
- 苏教版小学六年级数学上册期中测试卷
- 篇三:小学数学学困生辅导计划_
- 九大作业票安全管理制度
- 云南保山市隆阳区旅游业发展的SWOT分析要点
- 人教版语文第四单元试卷
- 锦世流年。_六年级作文
- 七年级道德与法治教材分析
- 乡风文明示范村申报材料
- 房屋转让协议书范本
- 2022年九年级化学上册绪言化学使世界变得更加绚丽多彩教案(新版)
- (名师整理)最新部编人教版语文冲刺中考压轴模拟检测试题(含答案
- 公路工程竣工资料档案完整目录
- 初中化学趣味知识竞赛试题(附答案)
- 部编版语文六年级上册第二单元测试题附答案
- 中国银行述职报告3000字
- 【化学】重庆市杨家坪中学2013-2014学年高二上学期第一次月考试
- 小学数学三年级上册第一单元两三位数除以一位数教案及反思
- 行政部工作内容及流程概述Microsoft-Word-文档
- GS71108ATP_06中文资料
- (贵阳专版)2022届中考历史总复习试题分析及备考方略