目录

1、开发环境

2、FreeModbus 软件包

添加软件包

使用方法

数据缓冲区

Modbus 数据处理回调接口

初始化配置流程

正常使用流程

异常处理流程

API 详解

写单个保持寄存器

读多个保持寄存器

读写多个保持寄存器

读多个输入寄存器

写单个线圈

写多个线圈

读多个线圈

读多个离散输入

注意事项

联系方式

3、FreeModbus相关配置

4、主机测试代码 sample_mb_master.c 

5、测试效果


1、开发环境

开发环境:使用 RT-Thread 实时操作系统,版本 rt-thread [4.0.2] 。

开发软件:使用 RT-Thread Studio (目前最新版本1.1.3)。

rtthread freemodbus rtthread freemodbus主机_stm32

2、FreeModbus 软件包

添加软件包

在RT-Thread 项目中添加 freemodbus软件包,如下图所示:

rtthread freemodbus rtthread freemodbus主机_寄存器_02

rtthread freemodbus rtthread freemodbus主机_rtthread freemodbus_03

文件结构

源文件	描述
FreeModbus\modbus\mb.c	给应用层提供Modbus从机设置及轮询相关接口
FreeModbus\modbus\mb_m.c	给应用层提供Modbus主机设置及轮询相关接口
FreeModbus\modbus\ascii\mbascii.c	ASCII模式设置及其状态机
FreeModbus\modbus\functions\mbfunccoils.c	从机线圈相关功能
FreeModbus\modbus\functions\mbfunccoils_m.c	主机线圈相关功能
FreeModbus\modbus\functions\mbfuncdisc.c	从机离散输入相关功能
FreeModbus\modbus\functions\mbfuncdisc_m.c	主机离散输入相关功能
FreeModbus\modbus\functions\mbfuncholding.c	从机保持寄存器相关功能
FreeModbus\modbus\functions\mbfuncholding_m.c	主机保持寄存器相关功能
FreeModbus\modbus\functions\mbfuncinput.c	从机输入寄存器相关功能
FreeModbus\modbus\functions\mbfuncinput_m.c	主机输入寄存器相关功能
FreeModbus\modbus\functions\mbfuncother.c	其余Modbus功能
FreeModbus\modbus\functions\mbutils.c	一些协议栈中需要用到的小工具
FreeModbus\modbus\rtu\mbcrc.c	CRC校验功能
FreeModbus\modbus\rtu\mbrtu.c	从机RTU模式设置及其状态机
FreeModbus\modbus\rtu\mbrtu_m.c	主机RTU模式设置及其状态机
FreeModbus\modbus\tcp\mbtcp.c	TCP模式设置及其状态机
FreeModbus\port\port.c	实现硬件移植部分接口
FreeModbus\port\portevent.c	实现从机事件移植接口
FreeModbus\port\portevent_m.c	实现主机事件及错误处理移植接口
FreeModbus\port\portserial.c	从机串口移植
FreeModbus\port\portserial_m.c	主机串口移植
FreeModbus\port\porttimer.c	从机定时器移植
FreeModbus\port\porttimer_m.c	主机定时器移植
FreeModbus\port\user_mb_app.c	定义从机数据缓冲区,实现从机Modbus功能的回调接口
FreeModbus\port\user_mb_app_m.c	定义主机数据缓冲区,实现主机Modbus功能的回调接口
FreeModbus\samples\sample_mb_master.c	主机使用示例代码
FreeModbus\samples\sample_mb_slave.c	从机使用示例代码
FreeModbus\samples\README.md	示例代码说明文档
注:所有带_m后缀的文件为主机模式下必须使用的文件,如使用从机模式则无需这些文件。

使用方法

数据缓冲区

数据缓冲区定义的位置位于 FreeModbus\port\user_mb_app_m.c 文件顶部,共计 4种 数据类型。 FreeModbus从机默认使用 一维数组 作为缓存区数据结构,主机可以存储所有网内从机的数据,所以主机采用 二维数组 对所有从机节点数据进行存储。二维数组的列号代表寄存器、线圈及离散量地址,行号代表从机节点ID,但需要做减一处理,例如usMRegHoldBuf[2][1]代表从机ID为 3,保持寄存器地址为 1 的从机数据。

Modbus 数据处理回调接口

Modbus 一共有4种不同的数据类型,所有的 Modbus 功能都围绕这些数据类型进行操作。由于不同的用户数据缓冲区结构可能有所不同,那么对应的 Modbus 数据处理方式也就存在差异,所以用户需要把每种数据类型对应的操作,按照自己的数据缓冲区结构进行定制实现。 所有的 Modbus 数据处理回调接口如下:

rtthread freemodbus rtthread freemodbus主机_rtthread freemodbus_04

初始化配置流程

本协议栈所有配置参数都位于FreeModbus\modbus\include\mbconfig.h,目前协议栈支持主机及从机两种模式,并且支持两种模式同时开启。从机支持Modbus RTU 、Modbus ASCII 及Modbus TCP 3种模式,主机现在只支持常用的Modbus RTU模式。在使用主机的过程中,用户需要对广播的转换延时时间、命令响应超时时间及从机数量做以配置。需要注意的是,目前协议栈只支持从机地址连续,并且起始地址从1开始

正常使用流程

  1. 调用eMBMasterInit方法初始化Modbus主机协议栈,主机涉及到的一些硬件就在这个时候做了初始化
  2. 调用eMBMasterEnable方法启动Modbus主机
  3. 通过在线程或者定时器轮询调用eMBMasterPoll方法,轮询周期决定了命令的响应时间。
  4. 调用主机请求API方法,设定一定的请求超时时间,直到方法有结果后才会返回。如果方法执行成功并且命令是读命令,可以通过查看Modbus主机的数据缓冲区,获取最新从机数据。

具体的使用方法,可以参考 /samples 目录下的示例代码。调试 Modbus 的主从机程序可以在 PC 上使用 Modbus Poll 和 Modbus slave 软件配合调试。(参考 modbus slave和modbus poll使用说明)

异常处理流程

异常处理主要出现在主机正常使用过程中,所有的主机请求API的错误码都在第三章开头已经做以描述,针对的这些错误码,用户需要根据自己的产品特征去完成不同的动作。建议用户自己封装实现主机请求方法的重发机制,这样实现方式比较灵活,一般是在接收到帧数据出错及命令响应超时的错误码时需要重发,重发次数自动加一,如果重发次数超过设定值则认为从机掉线,以后所有只要是发给这个从机命令都被提前拦截掉;如果第二次重发命令响应成功,则自动清零该从机重发次数。 上述所有功能可以利用主机请求方法或者使用FreeModbus\port\portevent_m.c中的回调接口来实现,用户可以根据自己的需求灵活选择。

API 详解

Modbus 主机使用过程中与从机有很大不同,从机是需要被动等待主机请求,而主机则是主动发出请求,并接收处理从机响应。在主机发送广播请求的时候,从机不需要返回响应,所以广播请求适合主机的写从机数据命令,不适合读从机数据命令。 主机请求API中的所有方法的返回值格式都相同,返回值意义如下。

rtthread freemodbus rtthread freemodbus主机_寄存器_05

写单个保持寄存器

往从机某个保持寄存器中写入数据

eMBMasterReqErrCode eMBMasterReqWriteHoldingRegister( UCHAR ucSndAddr, 
                                                      USHORT usRegAddr,
                                                      USHORT usRegData,
                                                      LONG lTimeOut );

rtthread freemodbus rtthread freemodbus主机_stm32_06

读多个保持寄存器

读取多个保持寄存器中的数据

eMBMasterReqErrCode eMBMasterReqReadHoldingRegister( UCHAR ucSndAddr, 
                                                     USHORT usRegAddr,
                                                     USHORT usNRegs,
                                                     LONG lTimeOut );

rtthread freemodbus rtthread freemodbus主机_rtthread freemodbus_07

读写多个保持寄存器

先读多个寄存器,然后再写多个寄存器。

eMBMasterReqErrCode eMBMasterReqReadWriteMultipleHoldingRegister( UCHAR ucSndAddr,
                                                                  USHORT usReadRegAddr,
                                                                  USHORT usNReadRegs,
                                                                  USHORT * pusDataBuffer,
                                                                  USHORT usWriteRegAddr,
                                                                  USHORT usNWriteRegs,
                                                                  LONG lTimeOut )

rtthread freemodbus rtthread freemodbus主机_寄存器_08

读多个输入寄存器

读取多个输入寄存器中的数据

eMBMasterReqErrCode eMBMasterReqReadInputRegister( UCHAR ucSndAddr, 
                                                   USHORT usRegAddr,
                                                   USHORT usNRegs,
                                                   LONG lTimeOut );

rtthread freemodbus rtthread freemodbus主机_嵌入式_09

写单个线圈

往从机某个线圈中写入数据

eMBMasterReqErrCode eMBMasterReqWriteCoil( UCHAR ucSndAddr,
                                           USHORT usCoilAddr,
                                           USHORT usCoilData,
                                           LONG lTimeOut )

rtthread freemodbus rtthread freemodbus主机_stm32_10

写多个线圈

往从机多个线圈中写入数据

eMBMasterReqErrCode eMBMasterReqWriteMultipleCoils( UCHAR ucSndAddr,
                                                    USHORT usCoilAddr,
                                                    USHORT usNCoils,
                                                    UCHAR * pucDataBuffer,
                                                    LONG lTimeOut)

rtthread freemodbus rtthread freemodbus主机_嵌入式_11

读多个线圈

读取多个线圈中的数据

eMBMasterReqErrCode eMBMasterReqReadCoils( UCHAR ucSndAddr,
                                           USHORT usCoilAddr,
                                           USHORT usNCoils ,
                                           LONG lTimeOut )

rtthread freemodbus rtthread freemodbus主机_寄存器_12

读多个离散输入

读取多个离散输入中的数据

eMBMasterReqErrCode eMBMasterReqReadDiscreteInputs( UCHAR ucSndAddr,
                                                    USHORT usDiscreteAddr,
                                                    USHORT usNDiscreteIn,
                                                    LONG lTimeOut )

rtthread freemodbus rtthread freemodbus主机_rtthread freemodbus_13

注意事项

  • 从机支持Modbus RTU 、Modbus ASCII 及Modbus TCP 3种模式,主机现在只支持常用的Modbus RTU模式。
  • 目前协议栈只支持从机地址连续,并且起始地址从1开始

联系方式

3、FreeModbus相关配置

配置软件包 FreeModbus,勾选 Master mode,advanced configuration(寄存器地址等高级配置)根据实际情况配置(这里默认),使能示例 Eable master sample,modbus从机地址默认选择为 1 ,主机串口修改为 1 即 UART1,波特率修改为 9600。具体配置如下图所示:

rtthread freemodbus rtthread freemodbus主机_嵌入式_14

rtthread freemodbus rtthread freemodbus主机_嵌入式_15

rtthread freemodbus rtthread freemodbus主机_寄存器_16

rtthread freemodbus rtthread freemodbus主机_rtt_17

rtthread freemodbus rtthread freemodbus主机_嵌入式_18

rtthread freemodbus rtthread freemodbus主机_rtthread freemodbus_19

4、主机测试代码 sample_mb_master.c 

#include <rtthread.h>

#include "mb.h"
#include "mb_m.h"


#include "mbconfig.h"
#include "user_mb_app.h"
extern USHORT   usMRegInBuf[MB_MASTER_TOTAL_SLAVE_NUM][M_REG_INPUT_NREGS];
extern USHORT   usMRegHoldBuf[MB_MASTER_TOTAL_SLAVE_NUM][M_REG_HOLDING_NREGS];


#ifdef PKG_MODBUS_MASTER_SAMPLE
#define SLAVE_ADDR      MB_SAMPLE_TEST_SLAVE_ADDR
#define PORT_NUM        MB_MASTER_USING_PORT_NUM
#define PORT_BAUDRATE   MB_MASTER_USING_PORT_BAUDRATE
#else
#define SLAVE_ADDR      0x01
#define PORT_NUM        3
#define PORT_BAUDRATE   115200
#endif
#define PORT_PARITY     MB_PAR_NONE

#define MB_POLL_THREAD_PRIORITY  10
#define MB_SEND_THREAD_PRIORITY  RT_THREAD_PRIORITY_MAX - 1

#define MB_SEND_REG_START  2
#define MB_SEND_REG_NUM    2

#define MB_POLL_CYCLE_MS   500

static void send_thread_entry(void *parameter)
{
    eMBMasterReqErrCode error_code = MB_MRE_NO_ERR;
    eMBMasterReqErrCode error_code2 = MB_MRE_NO_ERR;
    eMBMasterReqErrCode error_code3 = MB_MRE_NO_ERR;
    static rt_uint16_t error_count = 0;
    static rt_uint16_t error_count2 = 0;
    static rt_uint16_t error_count3 = 0;
    USHORT data[2] = {0};

    while (1)
    {
        /* Test Modbus Master */
        data[0] = (USHORT)(rt_tick_get() / 10);
        data[1] = (USHORT)(rt_tick_get() % 10);

        /* 16 功能码 将多个数据连续写入到多个寄存器地址中 */
        error_code = eMBMasterReqWriteMultipleHoldingRegister(SLAVE_ADDR,          /* salve address */
                                                              MB_SEND_REG_START,   /* register start address */
                                                              MB_SEND_REG_NUM,     /* register total number */
                                                              data,                /* data to be written */
                                                              RT_WAITING_FOREVER); /* timeout */

        /* Record the number of errors */
        if (error_code != MB_MRE_NO_ERR)
        {
            error_count++;

            rt_kprintf("\r\n error_code = %d, error_count = %d \r\n",error_code,error_count);

            error_code = MB_MRE_NO_ERR;
        }

        /* 03 功能码 连续读取保持寄存器多个地址的数据 */
        error_code2 =eMBMasterReqReadHoldingRegister( SLAVE_ADDR, 0, 10, RT_WAITING_FOREVER );
        /* Record the number of errors */
        if (error_code2 != MB_MRE_NO_ERR)
        {
            error_count2++;

            rt_kprintf("\r\n \r\n error_code2 = %d, error_count2 = %d \r\n",error_code2,error_count2);

            error_code2 = MB_MRE_NO_ERR;
        } else
        {
            for(int i=0;i<10;i++)
            {
                rt_kprintf("HoldBuf[%d] = %d, ",i,usMRegHoldBuf[SLAVE_ADDR-1][i]);
            }
            rt_kprintf("\r\n");
        }


        /* 04 功能码 连续读取多个输入寄存器地址的数据 */
        error_code3 =eMBMasterReqReadInputRegister( SLAVE_ADDR, 0, 10, RT_WAITING_FOREVER );
        /* Record the number of errors */
        if (error_code3 != MB_MRE_NO_ERR)
        {
            error_count3++;

            rt_kprintf("\r\n \r\n error_code3 = %d, error_count3 = %d \r\n",error_code3,error_count3);

            error_code3 = MB_MRE_NO_ERR;
        } else
        {
            for(int i=0;i<10;i++)
            {
                rt_kprintf("InBuf[%d] = %d, ",i,usMRegInBuf[SLAVE_ADDR-1][i]);
            }
            rt_kprintf("\r\n");
        }

    }
}

static void mb_master_poll(void *parameter)
{
    eMBMasterInit(MB_RTU, PORT_NUM, PORT_BAUDRATE, PORT_PARITY);
    eMBMasterEnable();

    while (1)
    {
        eMBMasterPoll();
        rt_thread_mdelay(MB_POLL_CYCLE_MS);
    }
}

static int mb_master_samlpe(int argc, char **argv)
{
    static rt_uint8_t is_init = 0;
    rt_thread_t tid1 = RT_NULL, tid2 = RT_NULL;

    if (is_init > 0)
    {
        rt_kprintf("sample is running\n");
        return -RT_ERROR;
    }
    tid1 = rt_thread_create("md_m_poll", mb_master_poll, RT_NULL, 512, MB_POLL_THREAD_PRIORITY, 10);
    if (tid1 != RT_NULL)
    {
        rt_thread_startup(tid1);
    }
    else
    {
        goto __exit;
    }

    tid2 = rt_thread_create("md_m_send", send_thread_entry, RT_NULL, 512, MB_SEND_THREAD_PRIORITY, 10);
    if (tid2 != RT_NULL)
    {
        rt_thread_startup(tid2);
    }
    else
    {
        goto __exit;
    }

    is_init = 1;
    return RT_EOK;

__exit:
    if (tid1)
        rt_thread_delete(tid1);
    if (tid2)
        rt_thread_delete(tid2);

    return -RT_ERROR;
}
MSH_CMD_EXPORT(mb_master_samlpe, run a modbus master sample);

5、测试效果

测试硬件如同所示:

rtthread freemodbus rtthread freemodbus主机_嵌入式_20

modbus slave软件下载及使用方法参考modbus slave和modbus poll使用说明

rtthread freemodbus rtthread freemodbus主机_嵌入式_21

 串口将实时打印保存寄存器和输入寄存器的数据。

rtthread freemodbus rtthread freemodbus主机_rtt_22