文章目录[隐藏]
STM32F103芯片的 freemodbus RTU的移植和使用。
1 示例代码
代码示例上传在 gitee上,仓库地址为freemodbus移植示例
2 freemodbus介绍
Freemodbus是一个奥地利人写的Modbus协议。它是一个针对嵌入式应用的一个免费(自由)的通用MODBUS协议的移植。Modbus是一个工业制造环境中应用的一个通用协议。Modbus通信协议栈包括两层:Modbus应用层协议,该层定义了数据模式和功能;另外一层是网络层。本文以STM32F103ZET6的单片机为例,在Keil环境中讲述单片机作为从机实现 freeModbus RTU 模式的移植。源码的下载地址为https://www.embedded-experts.at/en/freemodbus-downloads/ ,源码主要包括 demo modbus doc tools 四个文件夹。Demo 文件夹中主要free modbus官方为我们新建好的各种平台的测试例程,加快我们的开发进度,其中包括 Win32平台、Linux平台、ARM平台等。本次使用的是STM32的平台,在源码中并没有STM32平台的示例,但是在 demo 中有一个 BARE 文件夹,我们可以在该文件夹的基础上进行源码的移植。Modbus文件夹下,主要放一些关于Modbus自身协议的源码,其中包括Modbus-Rtu、Modbus-Ascii、Modbus-Tcp等。doc主要放一些帮助和说明文件,tools就是放置一些需要的工具,在测试时可以使用Modbus Poll 和 Modbus Slave 进行调试测试。
2 freemodbus移植
由于freemodbus库没有在STM32上移植的示例,所以在移植STM32平台时只能在demo\BARE\port文件夹中进行从0开始的移植,在Keil工程中新建freemodbus文件夹,然后拷贝port文件夹和modbus文件夹下的所有内容到新建立的文件夹中,拷贝完成的文件夹如下图所示。
port文件夹下的文件是本次移植要修改的部分,其中 portserial.c 是串口的初始化和收发控制的实现,porttimer.c 是3.5T定时器的实现,portevent.c 无需修改。
2.1 串口的移植
本次设计使用的是modbus RTU模式,数据传输依靠串口来实现,所以需要将freemodbus库文件的数据的输入和输出定位在单片机的串口中,串口的移植在 portserial.c 中实现,在文件中主要实现了串口的初始化,串口的中断接收和发送的功能。
串口通讯的数据包由发送设备通过自身的TXD接口传输到接收设备得RXD接口,在协议层中规定了数据包的内容,具体包括起始位、主体数据(8位或9位)、校验位以及停止位,通讯的双方必须将数据包的格式约定一致才能正常收发数据。通讯帧格式如下图所示。
串口是一种最为常见的通讯方式,因为串口是一种全双工的通讯方式,但是modbus是一种主从也就是半双工的通讯方式,所以在移植时要设置串口为半双工的工作方式,即接收时关闭发送,发送时关闭接收。
2.2 定时器的移植
Modbus协议规定,在RTU模式中,消息的发送和接收至少以3.5个字符时间的停顿间隔为标志。实际使用中,网络设备不断侦测网络总线,计算字符间的停顿间隔时间,判断消息帧的起始点。当接收到第一个域(地址域)时,每个设备都进行解码以判断是否是发给自己的,在最后一个传输字符结束之后,一个至少3.5个字符时间的停顿标志标定了消息的结束,而一个新的消息可在此停顿后开始。初次之外Modbus还规定当串口的波特率小于9600bps时,两个数据帧之间至少有3.5个字符的时间间隔,当波特率大于等于19200bps时,两个数据帧的时间间隔以波特为19200bps时的3.5个字符长度为判断依据。
在freemodbus源码中,以定时器作为两个数据帧之间时间间隔的判断依据,源码的相关内容为:
/* If baudrate > 19200 then we should use the fixed timer values
* t35 = 1750us. Otherwise t35 must be 3.5 times the character time.
*/
if( ulBaudRate > 19200 )
{
usTimerT35_50us = 35; /* 1800us. */
}
else
{
/* The timer reload value for a character is given by:
*
* ChTimeValue = Ticks_per_1s / ( Baudrate / 11 )
* = 11 * Ticks_per_1s / Baudrate
* = 220000 / Baudrate
* The reload for t3.5 is 1.5 times this value and similary
* for t3.5.
*/
usTimerT35_50us = ( 7UL * 220000UL ) / ( 2UL * ulBaudRate );
}
if( xMBPortTimersInit( ( USHORT ) usTimerT35_50us ) != TRUE ) // 定时器初始化
{
eStatus = MB_EPORTERR;
}
分析源码可以看出,源码要求初始化一个50usTimerT35_50us的定时器,原因如下:源码展示了当波特率大于19200时,usTimerT35_50us赋值为35,这样赋值的原因是3.5个字符时35个比特位(每个字符包含一个起始位,8个数据位和一个停止位),所以3.5个字符所需要的发送时间为351/19200≈1823us。
3 源码解析
FreeModbus协议栈作为从机,等待主机传送的数据,当从机接收到一帧完整的报文后,对报文进行解析,然后响应主机,发送报文给主机,实现主机和从机之间的通信。为了更好的实现整个代码的设计,本文对FreeModbus源码进行简单的分析。在源码的demo.c中主要有三个函数分别是eMBInit(),eMBEnable()和eMBPoll(),接下来将对这三个函数分别进行解析。
源码解析图的源文件在仓库的 stm32f1xx_freemodbus/source_code_analysis 目录下
3.1 eMBEnable 解析
eMBInit()主要实现的功能是实现RTU模式和ASCALL模式的协议栈初始化,完成协议栈核心函数指针的赋值,包括Modbus协议栈的使能和禁止、报文的接收和响应、3.5T定时器中断回调函数、串口发送和接收中断回调函数,eMBRTUInit完成RTU模式下串口和3.5T定时器的初始化,需依据不同的开发板平台进行移植,本次设计的串口的波特率位9600,从机地址为0x05,主要函数的调用关系如下图所示。
3.2 eMBInit 解析
eMBEnable()函数主要设置Modbus协议栈工作状态eMBState为STATE_ENABLED;调用pvMBFrameStartCur()函数激活协议栈,函数调用关系如下图所示。
3.3 eMBPoll 解析
eMBPoll()函数主要查询事件的状态,根据不同的状态执行相应的处理函数,调用关系如下图所示。eMBPoll()函数在while(1)被不断的调用,使用switch来不断的对检测到的事件进行分类处理,当接收到一帧完整的数据时,此时在3.5T定时器中断中会将事件状态修改为EV_FRAME_RECEIVED,表示接收到一帧数据,然后会调用eMBRTUReceive()函数将数据从接收缓冲区中取出,判断接收的数据帧的地址是否和本机地址一致如果一致的情况下,则将事件状态修改为EV_EXECUTE,开始对数据帧进行解析处理。
在EV_EXECUTE状态下会首先遍历一个大小为16的结构体数组xFuncHandlers,寻找不同的功能码对应的执行函数,本文以读写保持寄存器为例进行解析,在eMBFuncReadHoldingRegister()函数中会对将数据帧进行拆分,取出功能码,寄存器地址,寄存器数量等数据,然后调用eMBRegHoldingCB()这个回调函数,这个函数源码库并没有实现,需要依据不同的功能进行编写,在编写时可以参考源码在其他平台下的移植实例,最终实现的效果是将数据从内存中取出放入对应的寄存器中,然后依据是否应答执行发送函数。
3.4 定时器超时 解析
FreeModbus是通过定时器判断启动接收准备完成和一帧数据接收结束的,所有的处理均在定时器的中断中进行,定时器中断的处理流程如下图所示。定时器中断的入口函数是prvvTIMERExpiredISR(),最终调用的接口是xMBRTUTimerT35Expired(),在中断处理函数中依据接收状态机的状态,执行不同的处理流程。如果接收模式处于初始化状态则释放一个EV_REDAY信号,然后关闭定时器并将接收状态机修改为空闲模式;如果接收机处于STATE_RX_RCV状态,表示此时完成了一个数据帧的接收,此时将释放一个EV_FRAME_RECEIVED接收完成信号,然后关闭定时器并将接收状态机修改为空闲模式,待eMBPoll()轮询后发现接收完成事件,对接收的内容进行解析和处理。
3.5 发送流程解析
本小节将详细说明发送过程的处理逻辑,发送的执行过程如下图所示。在eMBPoll()函数处理完接收的数据后开始调用发送函数peMBFrameSendCur()进行发送,最终调用的发送函数实际为eMBRTUSend(),在该发送函数中进行数据的组帧工作,同时修改发送状态机为eSndState为STATE_TX_XMIT,然后启动串口的发送字节函数xMBPortSerialPutByte()进行发送同时打开串口的发送功能关闭串口的接收功能,在每发送完一个字节的数据后会进入串口的发送中断处理函数中,最终调用的函数为xMBRTUTransmitFSM(),在该函数中会依据要发送的字节数循环的进行发送处理,待所有的要发送的内容发送完毕后,会释放EV_FRAME_SENT事件按,然后关闭串口的发送功能,打开串口的接收功能,等待接收下一个数据帧。
2.6 接收一帧数据流程 解析
接收过程的流程如下图所示。在串口的接收中断处理函数中执行具体的接收过程,最终调用的接收处理函数为xMBRTUReceiveFSM(),在这个函数中会根据不同的接收状态机执行相应的函数。
因为在初始化时,调用了eMBEnable()函数,在这个函数中启动了3.5T定时器,然后定时器中断将eRcvState状态修改为STATE_RX_IDLE,所以收到第一个字节的数据后首先执行case STATE_RX_IDLE中的内容,在这个分支里将收到的第一个数据存储到了ucRTUBuf接收缓冲区的第一个位置,然后将接收状态机修改为STATE_RX_RCV,接着每产生一个接收中断,就将收到的数据依次存储在ucRTUBuf接收缓冲区中。待主机发送完所有的数据后,子机端因为3.5T定时器的作用在一段时间内没有收到数据产生一个定时器中断,定时器中断中将释放一个接收完成信号,将xEventInQueue修改为TRUE,待eMBPoll()轮询到接收完成事件后进行接收数据的解析和处理。
版权声明:本文为CSDN博主「夏夜晚风_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_36310253/article/details/122478441
暂无评论