stm32f1xx-freemodbus-RTU 移植

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

生成海报
点赞 0

夏夜晚风_

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

stm32f1xx-freemodbus-RTU 移植

STM32F103芯片的 freemodbus RTU的移植和使用。1 示例代码 代码示例上传在 gitee上,仓库地址为freemodbus移植示例 2 freemodbus介绍 Freemodbus是一个奥地利人写的Modb

GPIO口详解、HAL库操作按键

本次博客知识来自于韦东山老师的7天物联网课程。 一、GPIO详解 1、STM32F103C8T6一共有48个引脚。 2、按A、B、C分组,每组16个引脚,编号为0~15,STM32F103C8T6