FreeModbus添加主机功能

经过前面几个夜晚的学习,此刻已经在FreeModbus中添加了主模式的代码,特此记录下添加心得体会。代码添加还是很容易,就是要保证数据准确的发送出去

1. 添加前的思考

在添加主模式代码前,深入的分析了FreeModbus源码,了解了其中软件协议才敢开始加代码。不过说来说去也就那么点东西,毕竟代码还是很简单。

  • 掌握FreeModbus事件驱动的 "源动力"
    针对于从模式设备,所有动作的执行,基本都来自于主机,即串口命令(中断)。有了数据和中断,各个状态机才能行动起来。
  • 了解FreeModbus的接口
    不管是RTU模式还是ASCII模式,他们接口都是固定的。都需要将他们的接口注册到modbus中,使用全局的函数指针管理起来。这样调用时直接使用函数指针,而不用关心具体接口的名字。
  • 了解常用功能码的含义
    这个才能更好的解读function目录下解析数据的流程。

2. 添加工作模式(ASCII,RTU)接口

2.1 接口描述

当主模式使用时,打包帧时需要获取到从机地址、PDU缓存、发送数据大小,所以需要添加几个接口获取。如下是RTU模式中添加的接口。

//下面是主机添加的接口,统一即可
typedef struct {
    void    (*pvGetPduBuffer)(UCHAR **ucPduBuffer);
    USHORT  (*pusGetPduSndBufCount)();
    void    (*pvSetPduSndBufCount)(USHORT len);
    UCHAR   (*pusGetDstAddr)();
    void    (*pvSetDstAddr)(UCHAR addr);
}mst_ops_t;

//====================================以下代码添加在mbrtu.c==========================
/*add by armwind for master send data,package by user.*/
static volatile USHORT usSndPduBufferCount = 0;
static volatile UCHAR usDstAddr = 0;

/*
*brief:get pdu buffer data
*              <----------- MODBUS PDU (1') ---------------->
*  +-----------+---------------+----------------------------+-------------+
*  | Address   | Function Code | Data                       | CRC/LRC     |
*  +-----------+---------------+----------------------------+-------------+
*/
static void vMBRTUGetPduBuffer(UCHAR **ucPduBuffer)
{
    *ucPduBuffer =(UCHAR *) &ucRTUBuf[MB_SER_PDU_PDU_OFF];
}

static USHORT usMBRTUGetPduSndBufCount()
{
    return  usSndPduBufferCount;
}

static void vMBRTUSetPduSndBufCount(USHORT len)
{
    usSndPduBufferCount = len;
}

static UCHAR usMBRTUGetDstAddr()
{
    return  usDstAddr;
}

static void vMBRTUSetDstAddr(UCHAR addr)
{
    usDstAddr = addr;
}

mst_ops_t rtu_mst_ops = {
    vMBRTUGetPduBuffer,
    usMBRTUGetPduSndBufCount,
    vMBRTUSetPduSndBufCount,
    usMBRTUGetDstAddr,
    vMBRTUSetDstAddr
};
  • vMBRTUGetPduBuffer:获取PDU缓存地址,这个就是volatile UCHAR ucRTUBuf[MB_SER_PDU_SIZE_MAX];去掉设备地址的部分。用于打包数据时使用。
  • usMBRTUGetPduSndBufCount:获取发送缓存数据长度,给FreeModbus发送接口传入数据长度
    //实现在mb.c poll中
            case EV_FRAME_SENT:
                master_ops->pvGetPduBuffer(&ucMBFrame); //master_ops即为注册的rtu或者ascii接口。
                peMBFrameSendCur(master_ops->pusGetDstAddr(), ucMBFrame, master_ops->pusGetPduSndBufCount());
    //            ( void )xMBPortEventPost( EV_EXECUTE );
                break;
    
  • vMBRTUSetPduSndBufCount:打包完数据后,需要将发送数据长度记录下,以便真正发送时使用。
  • usMBRTUGetDstAddr:需要访问的子设备地址,在真正执行发送数据时,传入缓存中,如下发送数据函数第一个参数就是子设备地址。
    eMBErrorCode
    eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
    {
        eMBErrorCode    eStatus = MB_ENOERR;
        USHORT          usCRC16;
    
        ENTER_CRITICAL_SECTION(  );
    
        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;
        //此处省略n行代码
        return eStatus;
    }
    
  • vMBRTUSetDstAddr:提前保存子设备地址信息,方便后面使用。

此外上面添加了一个函数指针结构体mst_ops_t,方便接口统一注册到modbus中。

2.2 接口注册过程

注册过程在eMBInit()相当的简单。接口注册过来后,用户无需关心关心是RTU模式还是ASCII,直接使用全局函数指针即可。

eMBErrorCode
eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
{
    eMBErrorCode    eStatus = MB_ENOERR;

    /* check preconditions */
    if( ( ucSlaveAddress == MB_ADDRESS_BROADCAST ) ||
        ( ucSlaveAddress < MB_ADDRESS_MIN ) || ( ucSlaveAddress > MB_ADDRESS_MAX ) )
    {
        eStatus = MB_EINVAL;
    }
    else
    {
        ucMBAddress = ucSlaveAddress;

        switch ( eMode )
        {
#if MB_RTU_ENABLED > 0
        case MB_RTU:
            pvMBFrameStartCur = eMBRTUStart;
            pvMBFrameStopCur = eMBRTUStop;
            peMBFrameSendCur = eMBRTUSend;
            peMBFrameReceiveCur = eMBRTUReceive;
            pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL;
            pxMBFrameCBByteReceived = xMBRTUReceiveFSM;
            pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM;
            pxMBPortCBTimerExpired = xMBRTUTimerT35Expired;
            //add by armwind for master mode.
            extern mst_ops_t rtu_mst_ops;
            master_ops = &rtu_mst_ops; //注册RTU接口

            eStatus = eMBRTUInit( ucMBAddress, ucPort, ulBaudRate, eParity );
            break;
#endif
#if MB_ASCII_ENABLED > 0
        case MB_ASCII:
            pvMBFrameStartCur = eMBASCIIStart;
            pvMBFrameStopCur = eMBASCIIStop;
            peMBFrameSendCur = eMBASCIISend;
            peMBFrameReceiveCur = eMBASCIIReceive;
            pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL;
            pxMBFrameCBByteReceived = xMBASCIIReceiveFSM;
            pxMBFrameCBTransmitterEmpty = xMBASCIITransmitFSM;
            pxMBPortCBTimerExpired = xMBASCIITimerT1SExpired;
            //add by armwind for master mode.
            extern mst_ops_t ascii_mst_ops;
            master_ops = &ascii_mst_ops;//注册ASCII接口

            eStatus = eMBASCIIInit( ucMBAddress, ucPort, ulBaudRate, eParity );
            break;
#endif
        default:
            eStatus = MB_EINVAL;
        }

3. 添加数据打包过程

3.1 接口介绍

针对各种功能码读写数据的格式还有些出入,为了统一接口,定义如下的接口。

eMBErrorCode
eMBMasterRequestRWData(UCHAR ucSndAddr,        /*dst address*/
    USHORT usAddress,       /*register address*/
    USHORT usNRegs,         /*register count*/
    UCHAR nBytes,           /*data count unit bytes,should nbytes = 2*usNRegs */
    UCHAR *data,            /*data to be write*/
    eMBRegisterMode eMode)  /* operation mode, read or write*/
  • ucSndAddr:目的设备地址
  • usAddress:当不是进行 “读写保持寄存器时”(读写同时进行),此寄存器为读寄存器地址,写寄存器存在了data指中。
  • usNRegs:读写寄存器的数量,
  • nBytes:读写寄存器的bytes字节数。
  • data:写入的数据,当进行 “读写保持寄存器时”(读写同时进行),此data保存了写入的信息,其结构如下所示。
    typedef struct {
        USHORT  usAddress;       /*register address*/
        USHORT  usNRegs;         /*register count*/
        UCHAR   nBytes;          /*data count unit bytes,should nbytes = 2*usNRegs */
        UCHAR   *data;           /*data write to usAddress*/
    } rw_hold_dat_t;
    
  • eMode:读写的工作模式,其枚举如下
    typedef enum
    {
        MB_REG_READ,                /*!< Read register values and pass to protocol stack. */
        MB_REG_WRITE,                /*!< Update register values. */
        //add by armwind for master
        MB_REG_WRITE_MULTIPLE,  /*just write data*/
        MB_REG_READWRITE,       /*read/write data at same time*/
        MB_REG_CMD_MAX_COUNT
    } eMBRegisterMode;
    

3.2 接口实现

由于各个功能码数据打包的流程不一样,而且读写模式是有区别的,所以每个文件中都需要添加一个此接口。这些文件如下所示:

-rw-r--r-- 1 armwind 1049089 12912 2月  14 10:56 mbfunccoils.c
-rw-r--r-- 1 armwind 1049089  7070 2月  14 10:58 mbfuncdisc.c
-rw-r--r-- 1 armwind 1049089 16944 2月  14 11:01 mbfuncholding.c
-rw-r--r-- 1 armwind 1049089  6541 2月  14 10:56 mbfuncinput.c

这里为了方便,以mbfuncdisc.c文件中添加的接口为例。由于 读离散输入状态这种情况主设备只有读的权限。

/**
    brief:read and write short data.
**/
eMBErrorCode
eMBMasterRequestRWDisc(UCHAR ucSndAddr,        /*dst address*/
    USHORT usAddress,       /*register address*/
    USHORT usNRegs,         /*register count*/
    UCHAR nBytes,           /*data count unit bytes,should nbytes = 2*usNRegs */
    UCHAR *data,            /*data to be write*/
    eMBRegisterMode eMode)  /* operation mode, read or write*/
{
    UCHAR           *ucMBPduBuf = NULL;
    UCHAR           sndLen = 0;
    eMBErrorCode    eStatus = MB_ENOERR;

    master_ops->pvGetPduBuffer(&ucMBPduBuf); //获取pdu buffer的指针
    master_ops->pvSetDstAddr(ucSndAddr); //保存子设备地址
    /* Check if we have registers mapped at this block. */
    if ((usAddress >= REG_DISC_START) && (usAddress + usNRegs <= REG_DISC_START + REG_DISC_SIZE)
        && (usNRegs < MB_PDU_FUNC_READ_DISCCNT_MAX)) {
        switch (eMode) {
            /* Read current values and pass to protocol stack. */
        case MB_REG_READ:
            ucMBPduBuf[MB_PDU_FUNC_OFF] = MB_FUNC_READ_DISCRETE_INPUTS; //读离散事件的功能码
            ucMBPduBuf[MB_PDU_FUNC_READ_ADDR_OFF] = (UCHAR)(usAddress >> 8);
            ucMBPduBuf[MB_PDU_FUNC_READ_ADDR_OFF + 1] = (UCHAR)usAddress; //寄存器
            ucMBPduBuf[MB_PDU_FUNC_READ_DISCCNT_OFF] = (UCHAR)(usNRegs >> 8);
            ucMBPduBuf[MB_PDU_FUNC_READ_DISCCNT_OFF + 1] = (UCHAR)usNRegs;//寄存器数量
            sndLen = 5;
            break;
        }
    }
    else
    {
        //LOGE("dst_addr:0x%x,reg_addr:0x%x,usNregs:%d,nBytes:%d,data:0x%x,eMode:%d",ucSndAddr,usAddress,usNRegs,nBytes,data,eMode)
        eStatus = MB_EINVAL;
    }
    master_ops->pvSetPduSndBufCount(sndLen); //保存发送数据长度
    (void)xMBPortEventPost(EV_FRAME_SENT); //前面已经把数据打包好了,激发发送状态机,启动真正的串口发送流程。

    return eStatus;
}

当然上面宗旨是要把数据打包好,打包准确,并最后由串口发送出去。

4. 实验演示

由于实例是在visual studio2015上模拟调试,物理设备上的调试需疫情过后了。下面这个例子能循环执行各种请求,查看打印的结果,可以判断发送数据的准确性。至于读取子设备响应数据,要在物理设备上调了。不过使用FreeModbus原生的接收流程,应该是没有问题的。

int main()
{
    const UCHAR     ucSlaveID[] = { 0xAA, 0xBB, 0xCC };

    if (eMBInit(MB_RTU, 0x0A, 1, 38400, MB_PAR_EVEN) != MB_ENOERR)
    {
        printf("can't initialize modbus stack!\r\n");
    }
    else if (eMBSetSlaveID(0x34, TRUE, ucSlaveID, 3) != MB_ENOERR)
    {
        printf("can't set slave id!\r\n");
    }
    else
    {
        if( eMBEnable(  ) == MB_ENOERR )
        {
            UCHAR dat[2] = {0xf8,0xf8};
            UCHAR dat2[8] = {0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe};
            UCHAR count = 0;
            rw_hold_dat_t hld_dat = { 2100,4,8,dat2 };
            while (1)
            {

                //printf("request write coils\n");
                if (count == 0) {
                    printf("request read coils\n");
                    eMBMasterRequestRWCoils(0x3a, 1000, 1, 2, dat, MB_REG_READ);
                }
                else if (count == 1) {
                    printf("request write coils\n");
                    eMBMasterRequestRWCoils(0x3a, 1000, 1, 2, dat, MB_REG_WRITE);
                }
                else if (count == 2) {
                    printf("request write multiple coils\n");
                    eMBMasterRequestRWCoils(0x3a, 1006, 4, 8, dat2, MB_REG_WRITE_MULTIPLE);
                }
                else if (count == 3) {
                    printf("request read disc\n");
                    eMBMasterRequestRWDisc(0x3a, 1000, 1, 2, dat, MB_REG_READ);
                }
                else if (count == 4) {
                    printf("request read holding\n");
                    eMBMasterRequestRWHolding(0x3a, 2000, 1, 2, dat, MB_REG_READ);
                }
                else if (count == 5) {
                    printf("request write holding\n");
                    eMBMasterRequestRWHolding(0x3a, 2000, 1, 2, dat, MB_REG_WRITE);
                }
                else if (count == 6) {
                    printf("request write multiple holding\n");
                    eMBMasterRequestRWHolding(0x3a, 2000, 4, 8, dat2, MB_REG_WRITE_MULTIPLE);
                }
                else if (count == 7) {
                    printf("request read/write multiple holding\n");
                    eMBMasterRequestRWHolding(0x3a, 2000, 1, 2, (UCHAR*)&hld_dat, MB_REG_READWRITE);
                }
                else if (count == 8) {
                    printf("request read input\n");
                    eMBMasterRequestRWInput(0x3a, 1000, 1, 2, dat, MB_REG_READ);
                }
                else {
                    printf("-----------------------------------------------------------------------------------------\n");
                    count = 0;
                    continue;
                }
                if (eMBPoll() != MB_ENOERR)
                    break;
                count++;
            }
        }

        ( void )eMBDisable(  );
    }
        ( void )eMBClose(  );
    return 0;
}

打印结果:
下面貌似有几个数据校验码算的有问题,可能逻辑上还有些问题,待稍后优化下吧。

-----------------------------------------------------------------------------------------
request read coils
0x3a,0x01,0x03,0xe8,0x00,0x01,0x79,0x31,
request write coils
0x3a,0x05,0x03,0xe8,0xf8,0xf8,0x79,0x31,
request write multiple coils
0x3a,0x0f,0x03,0xee,0x00,0x04,0x08,0xfe,
request read disc
0x3a,0x02,0x03,0xe8,0x00,0x01,0x08,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0x81,0xa9,
request read holding
0x3a,0x02,0x03,0xe8,0x00,0x01,0x3d,0x31,
request write holding
0x3a,0x05,0x07,0xd0,0xf8,0xf8,0x3d,0x31,
request write multiple holding
0x3a,0x05,0x07,0xd0,0xf8,0xf8,0x8b,0x8e,
request read/write multiple holding
0x3a,0x17,0x07,0xd0,0x00,0x01,0x08,0x34,
request read input
0x3a,0x01,0x03,0xe8,0x00,0x01,0x08,0x34,0x00,0x04,0x08,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0x1e,0x02,
-----------------------------------------------------------------------------------------
  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值