Nano130之 FreeModbus移植

写在前面:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。


目录

    • 一、FreeModbus获取及提取
    • 二、文件结构
    • 三、工程移植
    • 四、接口修改
        • 1、portevent.c 源文件
        • 2、portserial.c 源文件
        • 3、porttimer.c 源文件
    • 五、API 回调处理
        • 1、读输入寄存器
        • 2、保持寄存器
        • 3、线圈寄存器
        • 4、读离散输入寄存器
    • 六、流程


一、FreeModbus获取及提取

官网:https://www.embedded-solutions.at/zh-hans/freemodbus/

下载:https://sourceforge.net/projects/freemodbus.berlios/files/

当然,现在也有 v1.6版本:
https://www.embedded-experts.at/en/freemodbus-downloads/

v1.6相对的,因为 FreeModbus是一款开源的 Modbus协议栈,但是只有从机开源,主机源码是需要收费的。同时网上也没有发现比较好的开源的 Modbus主机协议栈,所以才开发这款支持主机模式的 FreeModbus协议栈。同时把 FreeModbus版本号更改为 v1.6,特性如下:

  • 新增加的主机源码与原有从机的风格及接口保持一致;
  • 支持主机与从机在同一协议栈运行;
  • 支持实时操作系统及裸机移植;
  • 为应用提供多种请求模式,用户可以选择阻塞还是非阻塞模式,自定义超时时间等,方便应用层灵活调用;
  • 支持所有常用的Modbus方法。

v1.6的可以看下面的几个链接:

https://www.embedded-solutions.at/files/freemodbus-v1.6-apidoc/

https://github.com/armink/FreeModbus_Slave-Master-RTT-STM32

https://www.zybuluo.com/armink/note/7278

此处使用的是 v1.5的版本,当下载解压 v1.5版本的 FreeModbus文件夹后,可以看到如下文件:

Nano130之 FreeModbus移植_第1张图片

文件提取:

实际用到的只有两个文件,…\freemodbus-v1.5.0\demo\BARE以及 …\freemodbus-v1.5.0\modbus,然后我们移植一下得到下面的列表(其实相当于把 …\demo\BARE的文件夹移到上层目录,其他的官方例子都不要了)
Nano130之 FreeModbus移植_第2张图片

二、文件结构

源文件 描述
…\modbus\mb.c 给应用层提供Modbus从机设置及轮询相关接口
…\modbus\ascii\mbascii.c ASCII模式设置及其状态机
…\modbus\functions\mbfunccoils.c 从机线圈相关功能
…\modbus\functions\mbfuncdisc.c 从机离散输入相关功能
…\modbus\functions\mbfuncholding.c 从机保持寄存器相关功能
…\modbus\functions\mbfuncinput.c 从机输入寄存器相关功能
…\modbus\functions\mbfuncother.c 其余Modbus功能
…\modbus\functions\mbutils.c 一些协议栈中需要用到的小工具
…\modbus\rtu\mbcrc.c CRC校验功能
…\modbus\rtu\mbrtu.c 从机RTU模式设置及其状态机
…\modbus\tcp\mbtcp.c TCP模式设置及其状态机
…\port\portevent.c 实现从机事件移植接口
…\port\portserial.c 从机串口移植
…\port\porttimer.c 从机定时器移植

三、工程移植

当完成上面的文件提取后,那么我们就需要进行工程文件并入了,添加构建成如下:
Nano130之 FreeModbus移植_第3张图片

由于我们一般只使用 RTU协议,所以你可以不添加 ASCII以及 TCP功能的源文件进来,又或者像我这样添加但把它忽略编译,对应的文件就是那两个有特殊符号(即不进行对其编译)的文件

接着,我们在 mbconfig.h文件中配置相应的功能
Nano130之 FreeModbus移植_第4张图片

Nano130之 FreeModbus移植_第5张图片
其实都很好认,我们主要配置的是上面的两个部分,第一张的选择使能 Modbus的对应协议,ASCII跟 TCP我们不需要,可以设置为 0;第二张则是打开相应的函数操作

在Port.h头文件中定义有ENTER_CRITICAL_SECTION()和EXIT_CRITICAL_SECTION( )用于临界段屏蔽中段。
例:

#define ENTER_CRITICAL_SECTION( )   __disable_irq()	//关总中断
#define EXIT_CRITICAL_SECTION( )    __enable_irq()	//开总中断

四、接口修改

接口调用及接口移植都在 …\freemodbus-v1.5.0\demo\BARE\port文件夹内,该文件夹内主要有以下需要用户移植的接口:

所属文件 接口 功能描述
portevent.c xMBPortEventInit 从机事件初始化
portevent.c xMBPortEventPost 从机发送事件
portevent.c xMBPortEventGet 从机获取事件
portserial.c vMBPortSerialEnable 从机串口中断使能
portserial.c xMBPortSerialInit 从机串口初始化
portserial.c xMBPortSerialPutByte 从机发送一个字节
portserial.c xMBPortSerialGetByte 从机接收一个字节
portserial.c prvvUARTTxReadyISR 从机数据发送为空中断调用函数
portserial.c prvvUARTRxISR 从机数据接收中断调用函数
porttimer.c xMBPortTimersInit 从机定时器初始化
porttimer.c vMBPortTimersEnable 从机计数使能
porttimer.c vMBPortTimersDisable 从机计数失能
porttimer.c prvvTIMERExpiredISR 从机定时中断调用函数

1、portevent.c 源文件

这里我们不需要修改什么,保持默认就好了,因为只是一些状态反馈而已

2、portserial.c 源文件

无论是 Modbus ASCII还是RTU模式,都以串口通讯做为载体,故有:

  • xMBPortSerialInit:串口功能配置;
  • vMBPortSerialEnable:收发中断的使能;
  • xMBPortSerialPutByte:数据发送;
  • xMBPortSerialGetByte:数据接收;

FreeModbus通过串口中断的方式接收和发送数据,采用这种做法可以节省程序等待的时间,并且也充分使用CPU的资源:

  • prvvUARTTxReadyISR:发送中断服务函数,把数组中的数据接连发送;
  • prvvUARTRxISR:接收中断服务函数,将收到的数据存入缓存数组;

涉及到串口的移植文件都位于 …\port\portserial.c文件中,修改如下:

/*
 * FreeModbus Libary: BARE Port
 * Copyright (C) 2006 Christian Walter 
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id: portserial.c,v 1.1 2006/08/22 21:35:13 wolti Exp $
 */

/* ----------------------- Platform includes --------------------------------*/
#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

/* ----------------------- User add includes ----------------------------------*/
#undef TRUE
#undef FALSE
#include "Nano100Series.h"
#include "bsp_uart.h"

/* ----------------------- static functions ---------------------------------*/
static void prvvUARTTxReadyISR( void );
static void prvvUARTRxISR( void );

/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
    /* If xRXEnable enable serial receive interrupts. If xTxENable enable
     * transmitter empty interrupts.
     */
    if(TRUE == xRxEnable)
    {
        /* 使能接收 FIFO阀值中断,和接收超时中断 */
        UART_ENABLE_INT(UART1, (UART_IER_RDA_IE_Msk | UART_IER_RTO_IE_Msk));
    }
    else
    {
        UART_DISABLE_INT(UART1, (UART_IER_RDA_IE_Msk | UART_IER_RTO_IE_Msk));
    }

    if(TRUE == xTxEnable)
    {
        UART_ENABLE_INT(UART1, UART_IER_THRE_IE_Msk);
    }
    else
    {
        UART_DISABLE_INT(UART1, UART_IER_THRE_IE_Msk);
    }
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    uint32_t parity;

    if(USER_UART_COM1 == ucPORT) {
//		if(8 == ucDataBits)
        ucDataBits = UART_WORD_LEN_8;

        switch(eParity) {
        case MB_PAR_NONE: {
            parity = UART_PARITY_NONE;
            break;
        }
        case MB_PAR_ODD: {
            parity = UART_PARITY_ODD;
            break;
        }
        case MB_PAR_EVEN: {
            parity = UART_PARITY_EVEN;
            break;
        }
        }

        /* Enable IP clock */
        CLK_EnableModuleClock(UART1_MODULE);

        /* Select IP clock source */
        CLK_SetModuleClock(UART1_MODULE,CLK_CLKSEL1_UART_S_HIRC,CLK_UART_CLK_DIVIDER(1));

        /* Set PB multi-function pins for UART1 RXD and TXD */
        SYS->PB_L_MFP &= ~( SYS_PB_L_MFP_PB4_MFP_Msk | SYS_PB_L_MFP_PB5_MFP_Msk);
        SYS->PB_L_MFP |= (SYS_PB_L_MFP_PB4_MFP_UART1_RX | SYS_PB_L_MFP_PB5_MFP_UART1_TX );

        SYS_ResetModule(UART1_RST);

        /* Configure UART1 */
        UART_SetLine_Config(UART1, ulBaudRate, ucDataBits, parity, UART_STOP_BIT_1);

        /* 设置接收超时时间 time = Baudrate clk */
        UART_SetTimeoutCnt(UART1, 0x10);

        /* 优先权 0最高,3最低 */
        NVIC_SetPriority(UART1_IRQn, 0);
        NVIC_EnableIRQ(UART1_IRQn);

        return TRUE;
    } else
        return FALSE;
}

BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
    /* Put a byte in the UARTs transmit buffer. This function is called
     * by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been
     * called. */
    UART_WRITE(UART1, ucByte);

    return TRUE;
}

BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
    /* Return the byte in the UARTs receive buffer. This function is called
     * by the protocol stack after pxMBFrameCBByteReceived( ) has been called.
     */
    *pucByte = UART_READ(UART1);

    return TRUE;
}

/* Create an interrupt handler for the transmit buffer empty interrupt
 * (or an equivalent) for your target processor. This function should then
 * call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that
 * a new character can be sent. The protocol stack will then call
 * xMBPortSerialPutByte( ) to send the character.
 */
static void prvvUARTTxReadyISR( void )
{
    pxMBFrameCBTransmitterEmpty(  );
}

/* Create an interrupt handler for the receive interrupt for your target
 * processor. This function should then call pxMBFrameCBByteReceived( ). The
 * protocol stack will then call xMBPortSerialGetByte( ) to retrieve the
 * character.
 */
static void prvvUARTRxISR( void )
{
    pxMBFrameCBByteReceived(  );
}

/**
  * @brief  UART1 IRQHandler.
  * @param  None.
  * @return None.
  */
void UART1_IRQHandler(void)
{
    __IO uint32_t u32IntSts = UART1->ISR;
    __IO uint32_t u32IntIER = UART1->IER;

#if 1
    if(u32IntSts & (UART_ISR_RDA_IS_Msk | UART_ISR_RTO_IS_Msk)) {
        while(UART_GET_RX_EMPTY(UART1) == 0) {
            prvvUARTRxISR();
        }
    }

    if(u32IntSts & UART_ISR_THRE_IS_Msk
            && u32IntIER & UART_IER_THRE_IE_Msk) {
        prvvUARTTxReadyISR();
    }

#else
    /* 发生接收阀值中断或者接收超时中断 */
    if(u32IntSts & (UART_ISR_RDA_IS_Msk | UART_ISR_RTO_IS_Msk)) {
        /* 读走接收 FIFO中所有的数据,直到接收 FIFO为空 */
        while(UART_GET_RX_EMPTY(UART1) == 0) {
            /* 从接收 FIFO中读一个数据 */
            Usart0.RxBuffer[Usart0.RxCounter++] = UART_READ(UART1);
            if(Usart0.RxCounter >= RxBUFFER_SIZE) {
                Usart0.RxCounter = 0;
            }
        }
        Usart0.Receiving_Time = 2;
    }

    if(u32IntSts & UART_ISR_THRE_IS_Msk
            && u32IntIER & UART_IER_THRE_IE_Msk) {

        /* 发送寄存器空中断,当使能 THRE后,
           只要 uart->THR空了,就会产生中断。
           所以,发送完字符串后必须关掉,
           否则会导致重复进入中断。
        */

    }

#endif
}

这里值得注意的是 Nano130是自带串口数据超时功能的,所以在接收处理上要加上 UART_IER_RTO_IE_Msk这个中断

上面所述的接收超时并不是我们一般用的超时接收处理,而是 Nano130在接收器中有一个 16 字节的 FIFO (每个字节加 3 个 bit的纠错位) 缓存来减少向 CPU 申请中断的次数,通过配置下图的寄存器位进行选择:
Nano130之 FreeModbus移植_第6张图片
这里最多只能一次接收 14个 byte才出发一次中断,相对于 16 字节的 FIFO缓存有预留;如果 RX FIFO中断触发级别设为 14,UART接收 14个字节才会发生 RDA(接收数据可得)中断,这样可以降低 CPU的负载;上面的情况,如果 RX只接收到 10个字节怎么办呢?这时候就要用到接收超时中断。当 RX FIFO中收到 1个字节以后,定时器就开始计数,如果定时器超时都没有再收到下一个字节就会发生接收超时中断(RTO)

第二个值得注意的是,发送中断处理是使用发送寄存器空中断;如果是 STM32的话,还有个发送完成中断,这里需要关注一下,所幸在 Nano130中并没有发送完成中断

3、porttimer.c 源文件

Modbus RTU协议中没有明显的开始符和结束符,而是通过帧与帧之间的间隔时间来判断的。如果在指定的时间内,没有接收到新的字符数据,那么就认为收到了新的帧。接下来就可以处理数据了。RTU通过时间来判断帧是否接受完成,自然需要单片机中的定时器配合。

  • xMBPortTimersInit:定时器配置,3.5个字符时间区分不同的帧;
  • vMBPortTimersEnable:重置 T3.5超时中断;
  • vMBPortTimersDisable:关闭定时器;
  • prvvTIMERExpiredISR:定时器超时中断服务函数;

涉及到定时器的移植文件都位于 …\port\porttimer.c文件中,修改如下:

/*
 * FreeModbus Libary: BARE Port
 * Copyright (C) 2006 Christian Walter 
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id: porttimer.c,v 1.1 2006/08/22 21:35:13 wolti Exp $
 */

/* ----------------------- Platform includes --------------------------------*/
#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

/* ----------------------- User add includes ----------------------------------*/
#undef TRUE
#undef FALSE
#include "Nano100Series.h"
#include "bsp_time.h"

/* ----------------------- static functions ---------------------------------*/
static void prvvTIMERExpiredISR( void );

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
    /*
    T3.5个字符时间区分不同的帧,即接收到的两个字符之间时间间隔小于3.5个字符
    时间时认为是同一个帧的,如果间隔大于3.5个字符时间则认为是不同帧的
    在一般的串口通信中,发送 1个字符需要:1位起始位,8位数据位,1位校验位(可无),
    1位停止位,总共 1+8+1+1 = 11位,3.5个字符时间就是 3.5 * 11 = 38.5位,
    假如波特率是 9600,那么传输 1位的时间是 1/9600 = 0.10416667(ms) ,
    这样,3.5个字符时间就大约是 38.5 * 0.10416667 = 4 ms ,即定时器需要的中断时间
    */
    uint32_t PrescalerValue = 0;

    /* Enable IP clock */
    CLK_EnableModuleClock(TMR1_MODULE);

    /* Timer1选择 HXT做时钟源 */
    CLK_SetModuleClock(TMR1_MODULE, CLK_CLKSEL1_TMR1_S_HXT, NULL);

    /* 初始化 Timer1;周期模式;n tick(非真正除频) */
    TIMER_Open(TIMER1, TIMER_PERIODIC_MODE, 50*usTim1Timerout50us);

	/* 分频为 100kHz,单位为 10us,最后计数基准为 50us */
	PrescalerValue = (uint32_t)(TIMER_GetModuleClock(TIMER1) / 100000) - 1;
	TIMER_SET_PRESCALE_VALUE(TIMER1, PrescalerValue);	// 修改预分频的值( PRESCALE_CNT + 1 ) < 256
	TIMER_SET_CMP_VALUE(TIMER1, 5 * usTim1Timerout50us);	// 修改比较寄存器的值

    /* 优先权 0最高,3最低 */
    NVIC_SetPriority(TMR1_IRQn, 1);

    /* 使能 Timer1中断 */
    TIMER_EnableInt(TIMER1);
    NVIC_EnableIRQ(TMR1_IRQn);

    return TRUE;
}

inline void
vMBPortTimersEnable(  )
{
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */

    // The user also needs to add time count cleanup function here
	/* 由于该系列没有清计数值功能,所以只能用重设比较值来清零 */
	TIMER_SET_CMP_VALUE(TIMER1, 400);	// 400 tick *10us = 4ms
    TIMER_Start(TIMER1);
}

inline void
vMBPortTimersDisable(  )
{
    /* Disable any pending timers. */
    TIMER_Stop(TIMER1);
}

/* Create an ISR which is called whenever the timer has expired. This function
 * must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that
 * the timer has expired.
 */
static void prvvTIMERExpiredISR( void )
{
    ( void )pxMBPortCBTimerExpired(  );
}

/**
  * @brief  TMR1 IRQHandler.
  * @param  None.
  * @return None.
  */
void TMR1_IRQHandler(void)
{
    TIMER_ClearIntFlag(TIMER1);
    prvvTIMERExpiredISR();
}

在这里值得注意的是,定时器基准一定要做成 50us,不然有可能出现读写过慢或者读写多个寄存器时无效出错,还有就是在 vMBPortTimersEnable();函数内,在调用启动计数时,先清一下计数值;在 vMBPortTimersDisable();函数中,关闭计数后,可以不清;但不建议把计数清零功能只放在 vMBPortTimersDisable();关闭函数而不放在 vMBPortTimersEnable();函数内


五、API 回调处理

在 mb.h头文件中,大概 268行左右,是写有 Callback Function的声明及解释的,它的描述如下:

/*! \defgroup modbus_registers Modbus Registers
 * \code #include "mb.h" \endcode
 * The protocol stack does not internally allocate any memory for the
 * registers. This makes the protocol stack very small and also usable on
 * low end targets. In addition the values don't have to be in the memory
 * and could for example be stored in a flash.
* Whenever the protocol stack requires a value it calls one of the callback * function with the register address and the number of registers to read * as an argument. The application should then read the actual register values * (for example the ADC voltage) and should store the result in the supplied * buffer.
* If the protocol stack wants to update a register value because a write * register function was received a buffer with the new register values is * passed to the callback function. The function should then use these values * to update the application register values. */

后面的就是各个回调函数了

然后对应的返回状态代码:

/*! \ingroup modbus
 * \brief Errorcodes used by all function in the protocol stack.
 */
typedef enum
{
    MB_ENOERR,                  /*!< no error. */
    MB_ENOREG,                  /*!< illegal register address. */
    MB_EINVAL,                  /*!< illegal argument. */
    MB_EPORTERR,                /*!< porting layer error. */
    MB_ENORES,                  /*!< insufficient resources. */
    MB_EIO,                     /*!< I/O error. */
    MB_EILLSTATE,               /*!< protocol stack in illegal state. */
    MB_ETIMEDOUT                /*!< timeout error occurred. */
} eMBErrorCode;

1、读输入寄存器

/**
  *****************************************************************************
  * @Name   : 读输入寄存器
  * @Brief  : 对应功能码
  * 			0x04 -> 读单个或多个输入寄存器eMBFuncReadInputRegister
  * @Input  : pucRegBuffer   数据缓冲区
  *           usAddress:     寄存器地址
  *           usNRegs:       寄存器数量
  * @Output : none
  * @Return : Modbus状态信息
  * @Notice : 3 区
  * 		  对象类型:16位
  *****************************************************************************
**/
eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer,
               USHORT usAddress,
               USHORT usNRegs )
{
	
#if RTU_REG_INPUT
    eMBErrorCode eStatus = MB_ENOERR;
    USHORT iRegIndex;

    /* it already plus one in modbus function method. */
//    usAddress--;

    /* 请求地址大于起始地址 && 地址长度小于设定长度 */
    if ((usAddress >= REG_INPUT_START)
		&& (usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS))
    {
        iRegIndex = (USHORT)(usAddress - usRegInputStart);
        while ( usNRegs > 0 )
        {
            *pucRegBuffer++ = (UCHAR)(usRegInputBuf[iRegIndex] >> 8);
            *pucRegBuffer++ = (UCHAR)(usRegInputBuf[iRegIndex] & 0xFF);
            iRegIndex++;
            usNRegs--;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }

#else
	eMBErrorCode eStatus = MB_ENOREG;
	
    ( void )pucRegBuffer;
    ( void )usAddress;
    ( void )usNRegs;

#endif

    return eStatus;
}

2、保持寄存器

/**
  *****************************************************************************
  * @Name   : 保持寄存器
  * @Brief  : 对应功能码
  * 			0x03 -> 读单个或多个寄存器eMBFuncReadHoldingRegister
  * 			0x06 -> 写单个保持寄存器eMBFuncWriteHoldingRegister
  * 			0x10 -> 写多个保持寄存器eMBFuncWriteMultipleHoldingRegister
  * @Input  : pucRegBuffer:  数据缓冲区
  *           usAddress:     寄存器地址
  *           usNRegs:       寄存器数量
  * 		  eMode: 		 功能码
  * @Output : none
  * @Return : Modbus状态信息
  * @Notice : 4 区
  * 		  对象类型:16位
  *****************************************************************************
**/
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer,
                 USHORT usAddress,
                 USHORT usNRegs,
                 eMBRegisterMode eMode )
{
	
#if RTU_REG_HOLDING
    eMBErrorCode eStatus = MB_ENOERR;
    USHORT iRegIndex;

    /* it already plus one in modbus function method. */
//    usAddress--;

    if((usAddress >= REG_HOLDING_START) 
		&& (usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS))
    {
        iRegIndex = (USHORT)(usAddress - usRegHoldingStart);
        switch (eMode)
        {
        /* read current register values from the protocol stack. */
        case MB_REG_READ:
            while (usNRegs > 0)
            {
                *pucRegBuffer++ = (UCHAR)(usRegHoldingBuf[iRegIndex] >> 8);
                *pucRegBuffer++ = (UCHAR)(usRegHoldingBuf[iRegIndex] & 0xFF);
                iRegIndex++;
                usNRegs--;
            }
            break;

        /* write current register values with new values from the protocol stack. */
        case MB_REG_WRITE:
            while (usNRegs > 0)
            {
                usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
                usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
                iRegIndex++;
                usNRegs--;
            }
            break;
        }
    }
    else {
        eStatus = MB_ENOREG;
    }

#else
	eMBErrorCode eStatus = MB_ENOREG;
	
    ( void )pucRegBuffer;
    ( void )usAddress;
    ( void )usNRegs;
	( void )eMode;

#endif

    return eStatus;
}

3、线圈寄存器

/**
  *****************************************************************************
  * @Name   : 线圈寄存器
  * @Brief  : 对应功能码
  * 			0x01 -> 读单个或多个线圈eMBFuncReadCoils
  * 			0x05 -> 写单个线圈eMBFuncWriteCoil
  * 			0x0F -> 写多个线圈eMBFuncWriteMultipleCoils
  * @Input  : pucRegBuffer:  数据缓冲区
  *           usAddress:     寄存器地址
  *           usNRegs:       寄存器数量
  * 		  eMode: 		 功能码
  * @Output : none
  * @Return : Modbus状态信息
  * @Notice : 0 区
  * 		  对象类型:单个位
  *****************************************************************************
**/
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer,
               USHORT usAddress,
               USHORT usNCoils,
               eMBRegisterMode eMode )
{

#if RTU_REG_COILS
    eMBErrorCode eStatus = MB_ENOERR;
    USHORT iRegIndex, iRegBitIndex, iNReg;
    iNReg = usNCoils / 8 + 1;

    /* it already plus one in modbus function method. */
//    usAddress--;

    if ((usAddress >= REG_COIL_START) 
		&& (usAddress + usNCoils <= REG_COIL_START + REG_COIL_NREGS))
    {
        iRegIndex = (USHORT)(usAddress - usRegCoilStart) / 8;
        iRegBitIndex = (USHORT)(usAddress - usRegCoilStart) % 8;
        switch (eMode)
        {
        /* read current coil values from the protocol stack. */
        case MB_REG_READ:
            while (iNReg > 0)
            {
                *pucRegBuffer++ = xMBUtilGetBits(&usRegCoilBuf[iRegIndex++], iRegBitIndex, 8);
                iNReg--;
            }
            pucRegBuffer--;
            /* last coils */
            usNCoils = usNCoils % 8;
            /* filling zero to high bit */
            *pucRegBuffer = *pucRegBuffer << (8 - usNCoils);
            *pucRegBuffer = *pucRegBuffer >> (8 - usNCoils);
            break;

            /* write current coil values with new values from the protocol stack. */
        case MB_REG_WRITE:
            while (iNReg > 1)
            {
                xMBUtilSetBits(&usRegCoilBuf[iRegIndex++], iRegBitIndex, 8, *pucRegBuffer++);
                iNReg--;
            }
            /* last coils */
            usNCoils = usNCoils % 8;
            /* xMBUtilSetBits has bug when ucNBits is zero */
            if (usNCoils != 0)
            {
                xMBUtilSetBits(&usRegCoilBuf[iRegIndex++], iRegBitIndex, usNCoils, *pucRegBuffer++);
            }
            break;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }

#else
	eMBErrorCode eStatus = MB_ENOREG;
	
    ( void )pucRegBuffer;
    ( void )usAddress;
    ( void )usNCoils;
	( void )eMode;

#endif

    return eStatus;
}

4、读离散输入寄存器

/**
  *****************************************************************************
  * @Name   : 读离散输入寄存器
  * @Brief  : 对应功能码
  * 			0x02 -> 读单个或多个离散输入寄存器eMBFuncReadDiscreteInputs
  * @Input  : pucRegBuffer:  数据缓冲区
  *           usAddress:     寄存器地址
  *           usNRegs:       寄存器数量
  * @Output : none
  * @Return : Modbus状态信息
  * @Notice : 1 区
  * 		  对象类型:单个位
  *****************************************************************************
**/
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer,
                  USHORT usAddress,
                  USHORT usNDiscrete )
{

#if RTU_REG_DISCRETE
    eMBErrorCode eStatus = MB_ENOERR;
    USHORT iRegIndex, iRegBitIndex, iNReg;
    iNReg = usNDiscrete / 8 + 1;

#if 1		// 测试
	uint16_t i;
	const char buf[] = {0x01,0x02,0x03,0x04,0x05,0x07,0x08,0x09,0x0A};
	
	for(i = 0;i < REG_DISCRETE_NREGS / 8;i++){
		usRegDiscreteBuf[i] = *(buf + i);
	}

#endif
    /* it already plus one in modbus function method. */
//    usAddress--;

    if ((usAddress >= REG_DISCRETE_START) 
		&& (usAddress + usNDiscrete <= REG_DISCRETE_START + REG_DISCRETE_NREGS))
    {
        iRegIndex = (USHORT)(usAddress - usRegDiscreteStart) / 8;
        iRegBitIndex = (USHORT)(usAddress - usRegDiscreteStart) % 8;

        while (iNReg > 0)
        {
            *pucRegBuffer++ = xMBUtilGetBits(&usRegDiscreteBuf[iRegIndex++],
                                             iRegBitIndex, 8);
            iNReg--;
        }
        pucRegBuffer--;
        /* last discrete */
        usNDiscrete = usNDiscrete % 8;
        /* filling zero to high bit */
        *pucRegBuffer = *pucRegBuffer << (8 - usNDiscrete);
        *pucRegBuffer = *pucRegBuffer >> (8 - usNDiscrete);
    }
    else
    {
        eStatus = MB_ENOREG;
    }
	
#else
	eMBErrorCode eStatus = MB_ENOREG;
	
    ( void )pucRegBuffer;
    ( void )usAddress;
    ( void )usNDiscrete;

#endif
	
    return eStatus;
}

最后,为了利于调试,我们可以接出官方设置的断言:

/* Modbus 断言调试调用函数 */
void __aeabi_assert(const char *x1,const char *x2,int x3)
{
    /* User can add his own implementation to report the file name and line number,
       ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */

    while(1);
}

六、流程

1、调用 eMBInit();初始化 Modbus从机协议栈,涉及到的一些底层硬件就在这个时候做了初始化

2、调用 eMBEnable();方法启动 Modbus协议栈

3、通过在线程或者定时器轮询调用 eMBPoll();方法进行处理,轮询周期决定了命令的响应时间。

/**
  * @brief  Main routine.
  * @param  None.
  * @return None.
  */
int main(void)
{
	eMBErrorCode eStatus = MB_ENOERR;

	System_Start();

	/* 初始化 modbus为 RTU方式,波特率 9600,无校验 */
	eStatus = eMBInit(MB_RTU, MB_DEVEICE_ADDR, USER_UART_COM1, 9600, MB_PAR_NONE);
	if( MB_ENOERR == eStatus)
		eMBEnable();	// 使能 modbus协议栈

    while(1)
    {
		(void)eMBPoll();	// 轮训查询	
    }
}

你可能感兴趣的:(Nuvoton)