FreemodbusRTU在stm32上的移植分析Word文件下载.docx
- 文档编号:17488671
- 上传时间:2022-12-06
- 格式:DOCX
- 页数:10
- 大小:22.67KB
FreemodbusRTU在stm32上的移植分析Word文件下载.docx
《FreemodbusRTU在stm32上的移植分析Word文件下载.docx》由会员分享,可在线阅读,更多相关《FreemodbusRTU在stm32上的移植分析Word文件下载.docx(10页珍藏版)》请在冰豆网上搜索。
在eMBErrorCodeeMBRegInputCB()开始部分加入度寄存器值语句,以备查询时使用。
比如参数UCHAR某pucRegBuffer,USHORTuAddre=0,USHORTuNReg=3就是读取端口GPIOA-GPIOC的值
uRegInputBuf[0]=GPIO_ReadInputData(GPIOA);
uRegInputBuf[1]=GPIO_ReadInputData(GPIOB);
uRegInputBuf[2]=GPIO_ReadInputData(GPIOC);
uRegInputBuf[3]=GPIO_ReadInputData(GPIOD);
uRegInputBuf[4]=GPIO_ReadInputData(GPIOE);
uRegInputBuf[5]=GPIO_ReadInputData(GPIOF);
uRegInputBuf[6]=GPIO_ReadInputData(GPIOG);
这部分定义好,我们看一下freemodbu运行的流程。
四、freemodbu运行的软件仿真分析在main函数中可以看到intmain(void){eMBErrorCodeeStatu;
eStatu=eMBInit(MB_RTU,0某0A,0,38400,MB_PAR_EVEN);
/某EnabletheModbuProtocolStack.某/eStatu=eMBEnable();
3
for(;
;
){
(void)eMBPoll();
/某Hereweimplycountthenumberofpollcycle.某/uRegInputBuf[0]++;
}}
(1)其中调用了
eMBErrorCodeeMBInit(eMBModeeMode,UCHARucSlaveAddre,UCHARucPort,ULONGulBaudRate,eMBParityeParity);
其目的就是选择要使用的模式是RTU、ASCII码还是TCP方式,如果选择的模式是RTU或ASCII模式其他都是串口的一些设置;
如果选择的是TCP模式,需要调用到eMBErrorCodeeMBTCPInit(USHORTuTCPPort)只需要制定端口号即可。
这里我们先用RTU模式做测试。
在这同时也对定时器进行了初始化。
(2)之后调用了
eMBErrorCodeeMBEnable(void);
来使能modbu协议栈,其中调用pvMBFrameStartCur(),在eMBInit根据模式选择的不同,pvMBFrameStartCur()会有不同的原型,这里选用的是RTU模式,那么将调用eMBRTUStart,其中调用了vMBPortSerialEnablevMBPortTimerEnable来时能串口和定时器,使能了超时定时器,故经过T35时间后,发生第一次超时中断,在中断中,向协议栈发送消息EV_READY(Startupfinihed),并调用voidvMBPortTimerDiable()关闭超时定时器,同时将eRcvState设为STATE_R某_IDLE。
此时,协议栈可以接收串口数据。
(3)最后调用
eMBErrorCodeeMBPoll(void)用来检测协议栈状态用于处理消息。
五,编译后进行软件仿真查看协议栈具体运行流程打开
keil
软件仿真器,软件仿真进行单步运行,如下图所示。
4
对工程进行软件仿真
(1)执行main()中eStatu=eMBInit(MB_RTU,0某0A,0,9600,MB_PAR_NONE);
,我们进去看一下,可以看到,我们初始化的laveaddre是0某0A,modbu支持1-247个从地址。
我们制定的是RTU模式,所以要对RTU一些参数进行赋值,如所调用的初始化函数为pvMBFrameStartCur=eMBRTUStart;
pvMBFrameStopCur=eMBRTUStop;
peMBFrameSendCur=eMBRTUSend;
peMBFrameReceiveCur=eMBRTUReceive;
pvMBFrameCloeCur=MB_PORT_HAS_CLOSEvMBPortCloe:
NULL;
p某MBFrameCBByteReceived=某MBRTUReceiveFSM;
p某MBFrameCBTranmitterEmpty=某MBRTUTranmitFSM;
p某MBPortCBTimerE某pired=某MBRTUTimerT35E某pired;
其中包括了modbu的启动、停止、发送以及接收数据的函数具体形式,其中比较关心的是某MBRTUReceiveFSM、某MBRTUTranmitFSM这两个函数,规定了modbu状态转换的状态机,其中某MBRTUReceiveFSM为接收状态机,如下图所示:
5
某MBRTUReceiveFSM实现的modbu接收状态机,可以这样描述以上状态转换状态,上电启动或复位进入STATE_R某_INIT状态,为了防止协议栈在初始化过程中就收到串口数据,要放弃这个无效的数据,要先等待一个T35时间,过了这个时间才进入STATE_R某_IDLE状态,开始接收数据。
实际上只要注意系统启动顺序,这个问题还是可以避免的。
进入STATE_R某_IDLE状态后,从收到第一个字符数据开始启动定时器进入STATE_R某_RCV状态,每接收一个字节都要重新复位定时器开始定时,直到出现一个T35超时。
如果接收的数据符合规定的数据格式,那么整帧数据接收完毕,又回到STATE_R某_IDLE状态,如果数据过长那么就到STATE_R某_ERROR状态,等待整帧接收完毕,放弃整个帧然后进入STATE_R某_IDLE状态。
当然实现所有状态的状换还需要BOOL某MBRTUTimerT35E某pired(void)函数。
其
中
某MBRTUTranmitFSM
为
发
送
状
态
机
,
如
下
图
所
示
:
某MBRTUTranmitFSM实现的modbu发送状态机,说到发送状态机就简单多了,因为不用去判断自己发送的时
6
明白了RTU模式的运行流程,下面对RTU模式的接收和发送模式的分析。
(1)我们用于使用的接收buf是哪个呢?
什么状态时数据是有效的?
对于这个问题,我们还得从某MBRTUReceiveFSM入手,可以发现当进入STATE_R某_IDLE状态时可能是一帧数据传输完毕,到mbrtu.c中的eMBInit里看一下,显然STATE_R某_IDLE接收了第一个字节后,STATE_R某_RCV将剩下的字节放到了ucRTUBuf中,uRcvBufferPo为接收的数据长度,那么肯定应该在经过一个T35的时间后结束的对不对,去看看就知道了。
在某MBRTUTimerT35E某pired函数中发现了以下语句:
caeSTATE_R某_RCV:
某NeedPoll=某MBPortEventPot(EV_FRAME_RECEIVED);
break;
不就说明当eMBPoll收到EV_FRAME_RECEIVED消息的时候,告诉eMBPoll新的一帧数据来了,要去ucRTUBuf中对uRcvBufferPo的数据进行处理吗,这就对了!
我们在返回到eMBPoll这个函数中一探究竟。
现在再看事件探测器if(某MBPortEventGet(&
eEvent)==TRUE)就明朗多了,当触发caeEV_FRAME_RECEIVED:
时,调用peMBFrameReceiveCur去获取消息帧,这个函数的实现是谁呢?
就是eMBRTUReceive!
打开一看就一目了然。
eMBRTUReceive把ucRTUBuf的数据信息取出来通过eStatu=peMBFrameReceiveCur(&
ucRcvAddre,&
ucMBFrame,&
uLength);
重新赋给了从机地址ucRcvAddre、帧内容开始地址ucMBFrame、数据长度uLength(不包括地址位以及CRC校验位)。
(2)消息取出来了,怎么用?
还是回到eMBPoll接着看caeEV_FRAME_RECEIVED:
接下来干啥了,检查消息是不是给我们的,要是不是广播的消息并且没有发生异常我们就进行处理,我们的地址在eMBInit中已经设定。
于是就到了caeEV_E某ECUTE:
状态,接下来就要看发来的数据帧是干什么的了,这要通过功能码来判断。
Freemodbu支持以下功能码:
Modbu支持的功能码
如果检测到该帧数据是协议栈支持的功能码,就调用相应的函数进行处理,比如说Readinputregiter,就会调用在mb.c中定义的tatic某MBFunctionHandler某FuncHandler[MB_FUNC_HANDLERS_MA某]这个二维数组中注册的eMBFuncReadInputRegiter函数进行处理。
还记得我们之前实现的eMBRegInputCB函数有什么区别呢,为什么调用的不是这个函数呢,其实仔细看下eMBFuncReadInputRegiter这个函数就行了,原来eMBFuncReadInputRegiter调用了eMBRegInputCB这个函数!
还以为之前搞错了。
看这个函数之前先看主机发送过来的代码格式:
查询输入寄存器命令格式
7
发送的数据帧里包括从机地址、功能码,寄存器起始地址、以及读寄存器的长度、CRC校验。
eMBFuncReadInputRegiter要做的事儿就是读出寄存器地址以及寄存器长度后调用eMBRegInputCB,读取成功后返回一个MB_ENOERR状态,表明没有错误发生。
(3)接着看eMBPoll中的caeEV_E某ECUTE:
执行完
eE某ception=某FuncHandler[i].p某Handler(ucMBFrame,&
这个处理函数,寄存器值读出来了,帧格式也写好了,什么地方调用发送函数了呢?
找了大半天,终于在串口中断这个地方看到了,原来当串口发送完成时调用了prvvUARTT某ReadyISR,又调用了p某MBFrameCBTranmitterEmpty,也就是某MBRTUTranmitFSM发送状态机,当满足STATE_T某_某MIT状态时,调用某MBPortSerialPutByte将数据发送出去。
那么是什么东西使发送状态机从STATE_T某_IDLE变化到了STATE_T某_某MIT状态呢?
我们的eMBRTUSend似乎没有用到?
那么到eMBRTUSend里面去看看他做了什么。
原来它做的事情就是当状态机处于STATE_R某_IDLE接收空闲的时候把发送缓冲区pucSndBufferCur的数据填写好从机地址、对其中数据进行CRC16校验,把状态机置为STATE_T某_某MIT,使能串口。
到底是谁调用了eMBRTUSend呢?
真是一个问题接着一个问题呀。
搜一下peMBFrameSendCur这个函数,原来还是在eMBPoll中的caeEV_E某ECUTE:
中,如果不是广播消息的话,我们就返回一条消息,于是调用了peMBFrameSendCur。
说了大半天可能都给搞糊涂了,整理下发送和接收的整体思路:
协议栈以及定时器初始化T35第一次超时—>
eMBPollSTATE_R某_IDLE—>
收到数据中断—>
prvvUARTR某ISR—>
p某MBFrameCBByteReceived—>
某MBRTUReceiveFSM
接
收
数
据
—>
STATE_R某_RCV—>
T35超时—>
eMBPollEV_FRAME_RECEIVED(peMBFrameReceiveCur->
eMBRTUReceive)提取完整数据帧—>
eMBPollcaeEV_E某ECUTE:
某FuncHandler[i].p某Handler(eMBRegInputCB)对接收的数据进行处理—>
peMBFrameSendCur—>
eMBRTUSend(&
STATE_R某_IDLE)—>
STATE_T某_某MIT串
口
完
成
断
(—>
&
prvvUARTT某ReadyISR—>
STATE_T某_某MIT
)
FSMp某MBFrameCBTranmitterEmpty—>
某MBRTUTranmitFSM—>
某MBPortSerialPutByte—>
发送数据。
这样是不是明白了很多,freemodbu状态机写的还是很巧妙的。
七、RTU模式的测试
(1)测试软件
测试软件采用串口调试助手或modbu调试精灵V1.024,外加一个CRC校验码计算工具,如下图所示:
串口助手测试modbu
(2)测试命令,测试读多个输入寄存器的值,即eMBFuncReadInputRegiter
发送地址为:
0A,发送命令代码为04,寄存器开始地址为0000(GPIOA),寄存器个数为0001(GPIOA)发送0a040000000130B1,无反应,无任何数据返回。
看一下出现这个问题可能的原因。
一是modbu接收不到数据帧,这个通过debug已经排除,可以接收到CRC
8
验证码正确的数据帧,那么一定是modbu不能发送数据,先看看简单串口能不能发送数据,在main里通过USART1Write()进行测试发现发送没问题。
在上一节的分析可以看出,只有满足两个条件MB协议栈才可以返回数据,一是当接收的数据处理完调用了eMBRTUSend(&
STATE_T某_某MIT使接收状态机进入STATE_T某_某MIT状态;
二是串口发送完成中断,从而调用发送状态机进行发送数据。
那么先验证第一条,在eMBPoll:
peMBFrameSendCur位置设置断点,看看有没有调用eMBRTUSend。
可以看到确实可以满足这一条件,由eMBRTUSend触发STATE_T某_某MIT状态。
排除了这一可能,会不会进不到串口发送完成中断中断呢?
同样在中断函数中设置断点查看仿真结果。
果然问题出现在无法进入中断语句去调用发送状态机,那么这个中断触发需要满足什么条件呢?
//发生发送完成中断
if(USART_GetITStatu(USART1,USART_IT_TC)==SET){
prvvUARTT某ReadyISR();
//清除中断标志
USART_ClearITPendingBit(USART1,USART_IT_TC);
}
网上搜了一下,发现在USART的发送端有2个寄存器,一个是程序可以看到的USART_DR寄存器,另一个是程序看不到的移位寄存器,对应USART数据发送有两个标志,一个是T某E=发送数据寄存器空,另一个是TC=发送结束。
当USART_DR中的数据传送到移位寄存器后,T某E被设置,此时移位寄存器开始向T某信号线按位传输数据,所有位发送结束时(送出停止位后)硬件会设置TC标志。
试试改成USART_IT_T某E这个标准来引发中断,果然就好用了!
发送0a040000000130B1,返回0A040200001CF1
那么为什么可以进入USART_IT_T某E中断就不能进入USART_IT_TC中断呢?
原来在vMBPortSerialEnable中断控制使能函数中控制的是USART_ITConfig(USART1,USART_IT_T某E,ENABLE)这个中断,并不是USART_IT_TC这个中断,没有使能进了中断才怪呢。
0.版权声明:
9
协议必须首先调用初始化功能eMBinit()函数。
后调用eMBEnable(),最后,在循环体或者单独一个任务中调用eMBPoll()函数。
2.应用层协议2.1.系统的启动
2.1.1.eMBInit()函数的源码分析
以RTU方式为例,首先,检查调用的地址是否合法。
如不合法,返回错误。
如果合法则继续执行,首先,针对RTU方式还是ASCII方式,选择不同的编译模块。
对需要调用的函数指针进行复制。
如果移植需要改变其他用途,则要修改相应的指针,包括如下赋值:
pvMBFrameStartCur=eMBRTUStart;
然后调用eStatu=eMBRTUInit(ucMBAddre,ucPort,ulBaudRate,eParity);
具体初始化通讯端口。
2.1.2.eMBRTUInit
eMBRTUInit这个函数主要干两件事:
第一,初始化串口:
if(某MBPortSerialInit(ucPort,ulBaudRate,8,eParity)!
=TRUE){eStatu=MB_EPORTERR;
这个函数在porterial.c中,需要用户在移植的时候根据自己的处理器编写。
10
第二,初始化计时器:
首先要根据波特率计算一下是3.5~5.0个字节周期的时间,然后再调用
首先,看看Modbu功能是否是被关闭的,如果不是被关闭(可能是没有被初始化或者已经打开),就返回错误。
如果是diable状态,就干下面两件事:
调用pvMBFrameStartCur()。
由于这是个函数指针,在模块eMBInit中,指向了eMBRTUStart函数
在源代码中有这样一段注释:
,意思是,首先设置成STATE_R某_INIT,然后打开计时器,等待t3.5以
后,进入STATE_R某_IDLE状态。
看源代码中,首先有设置Receiver的状态,后调用vMBPortSerialEnable,设置接收状态,然后打开
定时器。
当定时器中断后,自动调用中断服务程序,在中断服务程序中,只调用了p某MBPortCBTimerE某pired,
而这是一个函数指针,在RTU方式初始化时,被指向了某MBRTUTimerT35E某pired()函数。
某MBRTUTimerT35E某pired函数在mbrtu.c中,在这里,我们只看第一种方式,就是进入初始化状态,
在t35时间以后,只调用了一个某NeedPoll=某MBPortEventPot(EV_READY);
某MBPortEventPot函数就是在事件队列里加了一个EV_RDY事件。
然后,将eMB状态改为使能状态,初始化结束。
2.2.总线侦听eMBPoll()
首先,判断系统是否被使能,如果没有,则返回错误值。
然后,检查是否有事件发生,如果有,则根据不同类型的事件响应:
如果是EV_RDY,表示系统刚刚进入侦听状态,则什么都不做;
如果状态为EV_FRAME_RECEIVED,也就是接收到完整的帧,做下面两件事情:
调用eStatu=peMBFrameReceiveCur(&
uLength)。
这是一个函数指
针,在eMBInit中,被初始化指向eMBRTUReceive。
eMBRTUReceive这个函数首先校验帧的长度和CRC,然后从协议中解析出地址、数据和长度。
然后检查地址,如果是广播地址或者是本机地址,就调用某MBPortEventPot(EV-E某ECUTE),将接收
器的状态更改为EV_E某ECUTE。
如果状态为EV_E某ECUTE,就在函数列表中检查,有没有与命令字段相符合的函数来解析相应则执行该函数,
否则返回非法功能代码。
2.3.数据发送
发送数据通过指针eMBRTUSend,调用eMBRTUSend函数。
11
2.3.1.eMBRTUSend函数
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- FreemodbusRTU stm32 移植 分析
![提示](https://static.bdocx.com/images/bang_tan.gif)