FreeModbus开源协议栈的移植和详解(三)

目录

概述

一、RTU文件夹的文件

二、mbrtu.c文件

2.1数据类型说明

2.2函数说明

2.1eMBRTUInit()函数

2.2eMBRTUStart()函数

2.3eMBRTUStop()函数

2.4eMBRTUReceive()函数

2.5eMBRTUSend()函数

2.6xMBRTUReceiveFSM()函数

2.7xMBRTUTransmitFSM()函数

2.8xMBRTUTimerT35Expired()函数

三、总结


概述

上篇文章分析了mb.c文件,我们知道,mb.c只是实现modbus协议的一个框架,具体的RTU、ASCII和TCP相关的代码再具体的文件夹中。这里我们以使用比较多的RTU为例,来分析一下RTU的实现。

一、RTU文件夹的文件

rtu文件夹下有四个文件,其说明如下:

文件名称 说明
mbcrc.c 这个文件只包含一个函数,就是标准的CRC16校验函数
mbcrc.h 包含CRC校验函数的函数声明
mbrtu.c 实现RTU协议的具体函数,rtu协议相关的实现函数都在这个文件中
mbrtu.h 头文件,包含rtu函数的声明

这里我们主要分析mbrtu.c文件,其他文件都比较简单。

二、mbrtu.c文件

先贴上代码,再一一说明。

/* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.* Copyright (c) 2006-2018 Christian Walter <cwalter@embedded-solutions.at>* All rights reserved.** Redistribution and use in source and binary forms, with or without* modification, are permitted provided that the following conditions* are met:* 1. Redistributions of source code must retain the above copyright*    notice, this list of conditions and the following disclaimer.* 2. Redistributions in binary form must reproduce the above copyright*    notice, this list of conditions and the following disclaimer in the*    documentation and/or other materials provided with the distribution.* 3. The name of the author may not be used to endorse or promote products*    derived from this software without specific prior written permission.** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.**//* ----------------------- System includes ----------------------------------*/
#include "stdlib.h"
#include "string.h"/* ----------------------- Platform includes --------------------------------*/
#include "port.h"/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbrtu.h"
#include "mbframe.h"#include "mbcrc.h"
#include "mbport.h"/* ----------------------- Defines ------------------------------------------*/
#define MB_SER_PDU_SIZE_MIN     4       /*!< Minimum size of a Modbus RTU frame. */
#define MB_SER_PDU_SIZE_MAX     256     /*!< Maximum size of a Modbus RTU frame. */
#define MB_SER_PDU_SIZE_CRC     2       /*!< Size of CRC field in PDU. */
#define MB_SER_PDU_ADDR_OFF     0       /*!< Offset of slave address in Ser-PDU. */
#define MB_SER_PDU_PDU_OFF      1       /*!< Offset of Modbus-PDU in Ser-PDU. *//* ----------------------- Type definitions ---------------------------------*/
typedef enum
{STATE_RX_INIT,              /*!< Receiver is in initial state. */STATE_RX_IDLE,              /*!< Receiver is in idle state. */STATE_RX_RCV,               /*!< Frame is beeing received. */STATE_RX_ERROR              /*!< If the frame is invalid. */
} eMBRcvState;typedef enum
{STATE_TX_IDLE,              /*!< Transmitter is in idle state. */STATE_TX_XMIT               /*!< Transmitter is in transfer state. */
} eMBSndState;/* ----------------------- Static variables ---------------------------------*/
static volatile eMBSndState eSndState;
static volatile eMBRcvState eRcvState;volatile UCHAR  ucRTUBuf[MB_SER_PDU_SIZE_MAX];static volatile UCHAR *pucSndBufferCur;
static volatile USHORT usSndBufferCount;static volatile USHORT usRcvBufferPos;/* ----------------------- Start implementation -----------------------------*/
eMBErrorCode
eMBRTUInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
{eMBErrorCode    eStatus = MB_ENOERR;ULONG           usTimerT35_50us;( void )ucSlaveAddress;ENTER_CRITICAL_SECTION(  );/* Modbus RTU uses 8 Databits. */if( xMBPortSerialInit( ucPort, ulBaudRate, 8, eParity ) != TRUE ){eStatus = MB_EPORTERR;}else{/* 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;}}EXIT_CRITICAL_SECTION(  );return eStatus;
}void
eMBRTUStart( void )
{ENTER_CRITICAL_SECTION(  );/* Initially the receiver is in the state STATE_RX_INIT. we start* the timer and if no character is received within t3.5 we change* to STATE_RX_IDLE. This makes sure that we delay startup of the* modbus protocol stack until the bus is free.*/eRcvState = STATE_RX_INIT;vMBPortSerialEnable( TRUE, FALSE );vMBPortTimersEnable(  );EXIT_CRITICAL_SECTION(  );
}void
eMBRTUStop( void )
{ENTER_CRITICAL_SECTION(  );vMBPortSerialEnable( FALSE, FALSE );vMBPortTimersDisable(  );EXIT_CRITICAL_SECTION(  );
}eMBErrorCode
eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
{BOOL            xFrameReceived = FALSE;eMBErrorCode    eStatus = MB_ENOERR;ENTER_CRITICAL_SECTION(  );assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX );/* Length and CRC check */if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN )&& ( usMBCRC16( ( UCHAR * ) ucRTUBuf, usRcvBufferPos ) == 0 ) ){/* Save the address field. All frames are passed to the upper layed* and the decision if a frame is used is done there.*/*pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF];/* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus* size of address field and CRC checksum.*/*pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC );/* Return the start of the Modbus PDU to the caller. */*pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF];xFrameReceived = TRUE;}else{eStatus = MB_EIO;}EXIT_CRITICAL_SECTION(  );return eStatus;
}eMBErrorCode
eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
{eMBErrorCode    eStatus = MB_ENOERR;USHORT          usCRC16;ENTER_CRITICAL_SECTION(  );/* Check if the receiver is still in idle state. If not we where to* slow with processing the received frame and the master sent another* frame on the network. We have to abort sending the frame.*/if( eRcvState == STATE_RX_IDLE ){/* First byte before the Modbus-PDU is the slave address. */pucSndBufferCur = ( UCHAR * ) pucFrame - 1;usSndBufferCount = 1;/* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;usSndBufferCount += usLength;/* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 );/* Activate the transmitter. */eSndState = STATE_TX_XMIT;vMBPortSerialEnable( FALSE, TRUE );}else{eStatus = MB_EIO;}EXIT_CRITICAL_SECTION(  );return eStatus;
}BOOL
xMBRTUReceiveFSM( void )
{BOOL            xTaskNeedSwitch = FALSE;UCHAR           ucByte;assert( eSndState == STATE_TX_IDLE );/* Always read the character. */( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte );switch ( eRcvState ){/* If we have received a character in the init state we have to* wait until the frame is finished.*/case STATE_RX_INIT:vMBPortTimersEnable(  );break;/* In the error state we wait until all characters in the* damaged frame are transmitted.*/case STATE_RX_ERROR:vMBPortTimersEnable(  );break;/* In the idle state we wait for a new character. If a character* is received the t1.5 and t3.5 timers are started and the* receiver is in the state STATE_RX_RECEIVCE.*/case STATE_RX_IDLE:usRcvBufferPos = 0;ucRTUBuf[usRcvBufferPos++] = ucByte;eRcvState = STATE_RX_RCV;/* Enable t3.5 timers. */vMBPortTimersEnable(  );break;/* We are currently receiving a frame. Reset the timer after* every character received. If more than the maximum possible* number of bytes in a modbus frame is received the frame is* ignored.*/case STATE_RX_RCV:if( usRcvBufferPos < MB_SER_PDU_SIZE_MAX ){ucRTUBuf[usRcvBufferPos++] = ucByte;}else{eRcvState = STATE_RX_ERROR;}vMBPortTimersEnable(  );break;}return xTaskNeedSwitch;
}BOOL
xMBRTUTransmitFSM( void )
{BOOL            xNeedPoll = FALSE;assert( eRcvState == STATE_RX_IDLE );switch ( eSndState ){/* We should not get a transmitter event if the transmitter is in* idle state.  */case STATE_TX_IDLE:/* enable receiver/disable transmitter. */vMBPortSerialEnable( TRUE, FALSE );break;case STATE_TX_XMIT:/* check if we are finished. */if( usSndBufferCount != 0 ){xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );pucSndBufferCur++;  /* next byte in sendbuffer. */usSndBufferCount--;}else{xNeedPoll = xMBPortEventPost( EV_FRAME_SENT );/* Disable transmitter. This prevents another transmit buffer* empty interrupt. */vMBPortSerialEnable( TRUE, FALSE );eSndState = STATE_TX_IDLE;}break;}return xNeedPoll;
}BOOL
xMBRTUTimerT35Expired( void )
{BOOL            xNeedPoll = FALSE;switch ( eRcvState ){/* Timer t35 expired. Startup phase is finished. */case STATE_RX_INIT:xNeedPoll = xMBPortEventPost( EV_READY );break;/* A frame was received and t35 expired. Notify the listener that* a new frame was received. */case STATE_RX_RCV:xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED );break;/* An error occured while receiving the frame. */case STATE_RX_ERROR:break;/* Function called in an illegal state. */default:assert( ( eRcvState == STATE_RX_INIT ) ||( eRcvState == STATE_RX_RCV ) || ( eRcvState == STATE_RX_ERROR ) );}vMBPortTimersDisable(  );eRcvState = STATE_RX_IDLE;return xNeedPoll;
}

2.1数据类型说明

该文件主要实现了数据的接收和发送的相关函数。
首先,用枚举定义了接收器和发送器的状态,列表说明如下:
接收器状态说明:

接收器状态 说明
STATE_RX_INIT 接收器已初始化
STATE_RX_IDLE 接收器空闲
STATE_RX_RCV 接收器正在接收
STATE_RX_ERROR 接收器错误

发送器状态说明:

发送器状态 说明
STATE_TX_IDLE 发送器空闲
STATE_TX_XMIT 正在发送

接着定义了一个数据缓存区、发送缓存指针、发送缓存长度、数据接收长度。

2.2函数说明

2.1eMBRTUInit()函数

该函数实现了RTU涉及到的UART和定时器的初始化。这里也只是提供一个函数,并未对函数进行实现。由于不同的处理器的UART和TIMER的初始化配置方式不同,所以这部分代码需要我们在移植的时候进行实现。具体等到移植的时候再来分析,这里我们重点看一下这个定时器时间是怎么配置的。
我们知道Modbus没有一帧结束的标记符,我们一般是通过超时时间来判断一帧是不是结束。超时时间一般设置为3.5个字符传送的时间。由于波特率不同对应的字符传送时间也不同,因此我们需要针对不同的波特率进行配置。详细说明如下:
根据eMBRTUInit()函数我们可以看出,FreeModbus将波特率大于19200的超时时间固定为1750us,其他的按照3.5个字符的传送时间来设置。这里我们定时器配置为每50us中断一次,因此我们只需要计算不同波特率下的定时器的中断次数即可。
1、baudrate>19200时
此种情况下次数T=1750/50=35;所以我们从代码中可以看到波特率大于19200的时候,次数固定为35。

2、baudrate≤19200时
我们知道串口一般发送的格式为一个起始位、8或者9位数据位、一位停止位、一般无校验或者一位校验位。加起来一帧大概有11个二进制位(不同的配置有所差别,这里取11个比较合适)。所以传送一个字符的时间就是11/baudrate(单位:s),传送3.5个字符的时间就是(7/2)*(11/baudrate)(单位:s),由于我们定时器50us中断一次,1s=20000个50us,所以对应的50us的次数就是(7/2)*(11/baudrate)*20000=(7*220000)/(2*baudrate);

2.2eMBRTUStart()函数

该函数比较简单,将接收器的状态设置为初始化状态,然后使能UART接收中断,禁止发送中断,使能定时器中断。

2.3eMBRTUStop()函数

该函数和eMBRTUStart()函数功能相反,禁止UART接收和发送中断,禁止定时器中断,不再相应UART发送过来的数据。

2.4eMBRTUReceive()函数

这个函数就是接收函数,从上篇文章中我们知道,当数据接收完成之后,轮询函数会获取一个接收完成事件,然后调用数据接收函数来接收数据,mb.c中的接收函数就是绑定的这个函数。由这个函数来做具体的数据接收的活。

详细看一下这个函数。
函数进来首先判断接收数据的长度有没有超过最大值,CRC校验可不可以通过,只有两者都满足了以后才对数据进行处理,这一步骤主要是确保接收到的数据的正确性。数据接收正确以后将接收到的地址、PDU的数据长度和PDU的数据数据帧传给对应的指针。(这里说明一下,PDU是去掉从机地址和CRC校验后的数据)

2.5eMBRTUSend()函数

这个函数就是发送函数,从上篇文章中我们知道,当数据处理完成之后,轮询函数会根据接收到的地址是不是广播帧来判断是否需要发送回帧,然后调用数据发送函数来发送数据,mb.c中的发送函数就是绑定的这个函数。由这个函数来做具体的数据发送的活。

该函数主要工作就是在发送PDU前面填充从机地址,后面填充CRC校验值,更新发送数据的长度,使能UART发送中断。

2.6xMBRTUReceiveFSM()函数

这个函数是UART串口中断接收处理函数和2.7中的发送函数都是在串口中断函数中调用的。其功能就是将串口接收到的数据放在ucRTUBuf数据缓冲区当中。

2.7xMBRTUTransmitFSM()函数

这个函数是串口发送中断处理函数,函数的主要功能就是将缓冲区中的数据发送出去。

2.8xMBRTUTimerT35Expired()函数

这个是超时处理函数,当接收超时时,说明接收完成,发送一个接收完成事件,然后关闭超时中断。

三、总结

mbrtu.c函数实现了modbus-rtu协议栈。它在整个协议栈中占据承上启下的作用个,向上,给modbus协议提供数据,向下接收硬件接收到的数据和往硬件发送数据。超时处理等等。其中一些功能还需要平台相关的接口的支持。这些在后面具体的移植中再进行分析。

FreeModbus开源协议栈的移植和详解(三)- RTU协议代码分析相关推荐

  1. FreeModbus开源协议栈的移植和详解(一)- FreeModbus的下载和文件结构

    FreeModbus开源协议栈的移植和详解(一) 引言 一.FreeModbus的获取 二.FreeModbus文件夹的介绍 三.Modbus文件夹介绍 四.小结 引言 很多做单片机或者嵌入式的朋友对 ...

  2. 短视频美颜sdk人脸编辑技术详解、美颜sdk代码分析

    短视频美颜sdk中人脸编辑技术可以将人像风格进行转变,小编认为这也是未来的美颜sdk的一个重要发展方向,下文小编将为大家讲解一下短视频美颜sdk中人脸编辑的关键点. 一.人脸编辑的细分关键点 1.年龄 ...

  3. PackageManagerService启动详解(三)之开始初始化阶段流程分析

      PKMS启动详解(三)之BOOT_PROGRESS_PMS_START阶段流程分析 Android PackageManagerService系列博客目录: PKMS启动详解系列博客概要 PKMS ...

  4. 主线剧情03-NXP-i.MX系列的u-boot移植基础详解

    u-boot 移植基础详解 本文系广泛撷取.借鉴和整理(相关的内容在网络上有很多,但很多相互抄,或者是版本太老,或者就是不通用的非常有平台针对性的步骤,碎片化泛滥,甚至就是有待分拣的垃圾厂,当然也有一 ...

  5. EasyPR中文开源车牌识别系统 开发详解

     在上篇文档中作者已经简单的介绍了EasyPR,现在在本文档中详细的介绍EasyPR的开发过程. 正如淘宝诞生于一个购买来的LAMP系统,EasyPR也有它诞生的原型,起源于CSDN的taotao ...

  6. TCP/IP 协议栈及 OSI 参考模型详解

    TCP/IP 协议栈及 OSI 参考模型详解 转载地址:http://www.codeceo.com/article/tcp-ip-osi-model.html OSI参考模型 OSI RM:开放系统 ...

  7. Java开源项目Hibernate包作用详解

    Java开源项目Hibernate包作用详解 本文引自:http://hi.baidu.com/nick6610/blog/item/70b58afa0d0eab9259ee90f7.html Jav ...

  8. linux 进程间通信 dbus-glib【实例】详解三 数据类型和dteeth(类型签名type域)(层级结构:服务Service --> Node(对象、object) 等 )(附代码)

    linux 进程间通信 dbus-glib[实例]详解一(附代码)(d-feet工具使用) linux 进程间通信 dbus-glib[实例]详解二(上) 消息和消息总线(附代码) linux 进程间 ...

  9. Android init.rc文件解析过程详解(三)

    Android init.rc文件解析过程详解(三) 三.相关结构体 1.listnode listnode结构体用于建立双向链表,这种结构广泛用于kernel代码中, android源代码中定义了l ...

最新文章

  1. JAVA(IO流)知识整理
  2. Runtime 系列 3-- 给 category 添加属性
  3. 无法更改域名 php网页,WordPress更改新域名后网站无法正常运行怎么办?
  4. 小明分享|8ms平台下工程源码分析
  5. js处理异常try{}catch(e){}
  6. 服务器维护,日志分析常用命令
  7. KMP算法中next数组的理解与算法的实现(java语言)
  8. LeetCode 1727. 重新排列后的最大子矩阵(前缀和+排序)
  9. 《追风筝的人》卡勒德·胡赛尼著小说成长的故事
  10. 在CentOS7环境下部署TiDB
  11. 认知觉醒是成长的首因,送3本硬核认知提升书
  12. PuttyPsftp
  13. 【UEFI实战】HII之uni文件
  14. 【人工智能】Google I/O 2023:让 AI 对每个人都更有帮助 Making AI more helpful for everyone
  15. centos光盘修复引导_CentOs7 修复 引导启动
  16. 微型计算机点火系统的摘要,丰田汽车点火系统故障诊断与排除的毕业论文教案.doc...
  17. 一般论文投稿应该注意哪些要求
  18. ISPRS2018/云检测:Cloud/shadow detection based on spectral indices for multi/hyp基于光谱指数的多/高光谱光学遥感成像仪云/影检测
  19. 解决Windows11/10本地账户改用Microsoft账户登录显示“发生了错误”的问题
  20. laravel框架下载指定版本

热门文章

  1. 2022年RPA将从IT领域继续扩展至非IT领域,非IT领域5大场景RPA应用
  2. 可编程的SQL是什么样的?
  3. Python零基础自学要多久?编程入门该怎么学?
  4. 云端软件平台与虚拟系统大比拼
  5. 职场PPT达人装酷的13条秘诀
  6. c++windows openSSL编译(ActivePerl-5.8.8.822-MSWin32-x86-280952.msi+openssl-1.0.2)
  7. 使用致远OA系统,打开Excel类型文件,提示office编辑程序未安装
  8. onenote2007 设置行间距
  9. 第二十三课.扩散模型
  10. sortable的基本属性