FreeModbus開源協議棧的移植和詳解(三)- RTU協議代碼分析

概述

上篇文章分析了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 <[email protected]>
 * 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協議提供數據,向下接收硬件接收到的數據和往硬件發送數據。超時處理等等。其中一些功能還需要平臺相關的接口的支持。這些在後面具體的移植中再進行分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章