modbus解析

所有的通信都是基于串口的,从串口的中断进行解析数据,再调用相应的数据。我们发送数据是从串口中发送数据,当接收到数据的时候,在产生中断在去调用我们接受数据的函数。
modbus基本的代码的工作原理是:
单片机每完成接收一个字符,就会进入一次中断处理函数,而在中断处理函数中,我们又调用了函数
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
该函数会间接调用回调函数,也就是说回调函数是由中断处理函数间接调用的。而函数

HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
1
2
决定了中断处理函数调用回调函数的频率,若Size为1,则每进入一次中断处理函数都会调用一次回调函数;
若Size为10,则每第十次进入中断处理函数时,才会调用回调函数。
freemodbus 是通过定时器判断启动接收准备完成和一帧数据接收结束的:
在STM32的USART中,是按照字节发送的,每接收或发送一个字节,产生一次中断。

例如上面的命令:01 03 00 04 00 02 85 ca

接收的步骤:

1)先接收01,然后产生一个中断,进入中断服务程序。

(中断服务程序功能:将01放进缓存中,然后重新定时器赋0值,并开启)

2)开始接收下一个数据:03。

3)依次类推,接收完所有的字节,然后拼接在一起,形成一个完整的数据帧。

如何区分1个完整的帧:

由于RT U没有起始和终止符,如何确定一帧的数据全部接收完了。

答:通过定时器。
在这里插入图片描述

两个帧数据之间,如果超过3.5个字符(字节)定时器的接收或发送周期,则产生中断,告诉本次帧接收完成了。

字节、帧、定时器之间的配合:

在USART每次接收完一个字节,产生中断函数,函数重新给3.5T的这个定时器赋初始值0并开启。

当最后一个字符(例如上例中的 ca)接收完后,对3.5T定时器进行最后一次赋0值。

3.5T定时器就一直计时,直到产生中断。此时,标志着一帧数据接收完成,开始了下一帧的数据。

备注:RTU模式中有一个1.5T的定时器,用来表示某个帧中的2个字节时间间隔超过了1.5T小于3.5T,则这个帧需要被丢弃。

1.freemodbus启动时,接收状态机状态eRcvState 是STATE_RX_INIT状态并启动定时器,表明处于准备接收阶段。当处于准备接收阶段时,就有数据接收,进入接收状态机后由于还没有产生定时器溢出中断,所以无法将eRcvState的状态从STATE_RX_INIT改为STATE_RX_IDLE,所以这时eRcvState状态一直为STATE_RX_INIT状态,直到这帧数据的结束,之后进入STATE_RX_IDLE空闲状态,等待接收下一帧数据;当处于准备接收阶段时,没有数据接收则定时器溢出产生中断,在中断服务函数中会将eRcvState 状态从STATE_RX_INIT设置成STATE_RX_IDLE,并推送事件EV_READY,表示准备好。

2.当处于接收状态机状态eRcvState处于STATE_RX_IDLE状态时,中断接收触发接收状态机将eRcvState设置成STATE_RX_RCV表明接收状态,每接收完一个字节的数据都将定时器清零重新计数,当接收完最后一个字节时,重新启动定时器之后没有接收到数据,定时器没有重新计数从而计数溢出产生中断,在中断服务函数中推送接收完成一帧数据事件EV_FRAME_RECEIVED事件,之后将eRcvState设置成STATE_RX_IDLE。在eMBPoll函数中轮训到EV_FRAME_RECEIVED事件,则从uvRTUBuf接收缓存中取出一帧数据进行CRC16校验,校验通过则进行相应的处理,最后应答主机。

pvMBFrameStartCur = eMBRTUStart;
pvMBFrameStopCur = eMBRTUStop;
peMBFrameSendCur = eMBRTUSend;
peMBFrameReceiveCur = eMBRTUReceive;
pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL;
pxMBFrameCBByteReceived = xMBRTUReceiveFSM;
pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM;
pxMBPortCBTimerExpired = xMBRTUTimerT35Expired;
全局变量
eMBRcvState 接收状态
eMBSndState 发送状态
初始化:

eMBInit(MB_RTU, 0x01, 0x01,115200, MB_PAR_NONE);
{
初始化3个方面: 1、串口初始化 2、定时器初始化 3、事件队列初始化
}
eMBEnable(); //启动FreeModbus
{
pvMBFrameStartCur( );-----> eMBRTUStart( void ) 函数指针调用 {eRcvState = STATE_RX_INIT;允许接收,关闭发送,开启定时器}
}

//大循环里事件查询,处理数据 数据使用中断接收,有接收状态机和发送状态机(xMBRTUReceiveFSM)(xMBRTUTransmitFSM)
eMBPoll( );
从机中断接收主机发来的数据
注意:eMBEnable(),使能定时器后,会产生一次定时器中断,在定时器中断里执行 xMBRTUTimerT35Expired();
初始化后 eRcvState = STATE_RX_INIT;case STATE_RX_INIT:xNeedPoll = xMBPortEventPost( EV_READY ); 发送接收准备好的事件
vMBPortTimersDisable( ); //关闭定时器
eRcvState = STATE_RX_IDLE;//接收处于空闲状态

从机接收数据流程:
中断接收

触发接收中断 prvvUARTRxISR( ); --》 pxMBFrameCBByteReceived( );—》最终调用xMBRTUReceiveFSM( void )(接收状态机)接收数据
xMBRTUReceiveFSM( void )(接收状态机) 详解
函数中定义:UCHAR ucByte;(用来临时保存接收到的数据)
1:( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte );
( *pucByte = USART_ReceiveData(USART2);)//接收到的数据放入 UCHAR ucByte;
switch (eRcvState): //进入STATE_RX_IDLE 把接收到的数据放入ucRTUBuf[]数组,切换状态eRcvState = STATE_RX_RCV ,开启定时器。
2:接收状态机 :第一次进入中断:(eRcvState = STATE_RX_IDLE ) case STATE_RX_IDLE: usRcvBufferPos = 0; ucRTUBuf[usRcvBufferPos++] = ucByte;eRcvState = STATE_RX_RCV;vMBPortTimersEnable( ); //开启定时器
第二次进入中断:(eRcvState = STATE_RX_RCV ) case STATE_RX_RCV: if( usRcvBufferPos < MB_SER_PDU_SIZE_MAX )
一直进入这一状态机持续接收数据,一直到接受完一帧数据
{ ucRTUBuf[usRcvBufferPos++] = ucByte; //一直接收 } else { eRcvState = STATE_RX_ERROR; //接收完成 } vMBPortTimersEnable( ); //定时器重启
ucRTUBuf[MB_SER_PDU_SIZE_MAX]; 接收的数据存到这个数组中 全局的接收后的数据存储详细地址。
从机接收01 03 00 00 00 01 84 0A
ucRTUBuf[0] = 01 设备地址
ucRTUBuf[1] = 03 功能码
ucRTUBuf[2] = 00 读保持寄存器的起始地址 高8位
ucRTUBuf[3] = 00 读保持寄存器的起始地址 低8位
ucRTUBuf[4] = 00 读保持寄存器的个数 高8位
ucRTUBuf[5] = 01 读保持寄存器的个数 低8位
ucRTUBuf[6] = 84 CRC 高8位
ucRTUBuf[7] = 0A CRC 低8位
3:没有数据可以接收,不再进入接收状态机,所以定时器不会重启,此时等待产生定时器中断 表明数据接收完成
prvvTIMERExpiredISR( ); --》 pxMBPortCBTimerExpired—xMBRTUTimerT35Expired
case STATE_RX_RCV:
xMBPortEventPost( EV_FRAME_RECEIVED ); //发送事件,接收完成事件
vMBPortTimersDisable( ); //关定时器
eRcvState = STATE_RX_IDLE; //接收空闲 可以再次接收

//eMBPoll函数轮询,发现“接收到报文”事件发生,调用peMBFrameReceiveCur函数,
//此函数指针在eMBInit被赋值eMBRTUReceive函数,最终调用eMBRTUReceive函数,
//从ucRTUBuf中取得从机地址、PDU单元和PDU单元的长度,然后判断从机地址地是否一致,
//若一致,上报“报文解析事件”EV_EXECUTE,(xMBPortEventPost( EV_EXECUTE ));
//“报文解析事件”发生后,根据功能码,调用xFuncHandlers[i].pxHandler( ucMBFrame, &usLength )对报文进行解析,此过程全部在eMBPoll函数中执行;

eMBErrorCode eMBPoll( void )
{
    static UCHAR   *ucMBFrame; //接受和发送报文的数据缓冲区
    static UCHAR    ucRcvAddress; //modebus从机地址
    static UCHAR    ucFunctionCode; //功能码
    static USHORT   usLength;//报文长度
    static eMBException eException; //错误码响应枚举

    int             i;
    eMBErrorCode    eStatus = MB_ENOERR; //协议栈错误码
    eMBEventType    eEvent; //事件标志枚举

    /* Check if the protocol stack is ready. */
    if( eMBState != STATE_ENABLED ) //检查协议是否使能
    {
        return MB_EILLSTATE; //协议栈未使能,返回协议栈无效错误码
    }

    /* Check if there is a event available. If not return control to caller.
     * Otherwise we will handle the event. */
    
    //查询事件
    if( xMBPortEventGet( &eEvent ) == TRUE )//此时的eEvent的作用是表示此时的线程的作用。
    {
        switch ( eEvent )
        {
        case EV_READY:
            break;

        case EV_FRAME_RECEIVED: /*接收到一帧数据,此事件发生*/
            eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );//它实际调用的是eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength ) ---》  进行数据长度和CRC校验(返回pucRcvAddress 设备地址)如果我的数据发送事件( void )xMBPortEventPost( EV_EXECUTE );
            <<
  最终执行函数 eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
 { 在这个函数中
       1:对数据长度和数据包CRC校验  
       if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN )&& ( usMBCRC16( ( UCHAR * ) ucRTUBuf, usRcvBufferPos ) == 0 ) )
       2:数据填充
*pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF];            *pucRcvAddress = ucRTUBuf[0];   
ucRcvAddress 保存了 ucRTUBuf[0] 即设备地址 0x01
*pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC );//减去CRC两位和头
usLength = 8-1-2 = 5
*pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF]; //从全局数组中获取功能码地址
*pucFrame = ( UCHAR * ) & ucRTUBuf[1];  // ucMBFrame中放的就是功能吗的地址
 }
            >>`
            if( eStatus == MB_ENOERR )
            {
                /* Check if the frame is for us. If not ignore the frame. */
                /*判断接收到的报文数据是否可接受,如果是,处理报文数据,是否是自己的地址或广播地址*/
                if( ( ucRcvAddress == ucMBAddress ) || ( ucRcvAddress == MB_ADDRESS_BROADCAST ) )
                {
                    ( void )xMBPortEventPost( EV_EXECUTE ); //修改事件标志为EV_EXECUTE执行事件。
                }
            }
            break;

        case EV_EXECUTE: //对接收到的报文进行处理事件
            ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF]; //获取PDU中第一个字节,为功能码,协议数据单元PDU(Protocol Data Unit)
            《《
  这里使用了一个C语言技巧,)可以这样理解 int * p;int a[10]; p = &a[x] ; *(p+i) = p[i] = a[x+i]
{
  static UCHAR   *ucMBFrame;
  在eMBRTUReceive函数中
  ucMBFrame = ( UCHAR * ) & ucRTUBuf[1]; //从全局数组中获取功能码地址 MB_SER_PDU_PDU_OFF = 1  MB_PDU_FUNC_OFF = 0
  因为此时 ucMBFrame =( UCHAR * &ucRTUBuf[1],所以  ucFunctionCode = ucMBFrame[0] 等价 ucFunctionCode = ucRTUBuf[1];--->  ucFunctionCode = 0x03
}
            》》
            eException = MB_EX_ILLEGAL_FUNCTION;
            for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ )//遍历数组
            {
                /* No more function handlers registered. Abort. */
                //查找是否有对应的功能处理函数。
                if( xFuncHandlers[i].ucFunctionCode == 0 )
                {
                    break;
                }
                else if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode ) /*根据报文中的功能码,处理报文*/
                {
                    //找到了进行处理 如果是03命令 就是eMBFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen )
                    //找到了进行处理 如果是06命令 eMBFuncWriteHoldingRegister( UCHAR * pucFrame, USHORT * usLen )
                    eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength );/*对接收到的报文进行解析*/
                    break;
                }
            }

            /* If the request was not sent to the broadcast address we
             * return a reply. */
            if( ucRcvAddress != MB_ADDRESS_BROADCAST )
            {
                if( eException != MB_EX_NONE )  /*接收到的报文有错误*/
                {
                    /* An exception occured. Build an error frame. */
                    usLength = 0;/*响应发送数据的首字节为从机地址*/
                    ucMBFrame[usLength++] = ( UCHAR )( ucFunctionCode | MB_FUNC_ERROR );/*响应发送数据帧的第二个字节,功能码最高位置1*/
                    ucMBFrame[usLength++] = eException;/*响应发送数据帧的第三个字节为错误码标识*/
                }
                eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength );/*modbus从机响应函数,发送响应给主机,回调函数处理完后,进入发送,先发送一个字节 ,然后进入发送状态机*/
            }
            break;

        case EV_FRAME_SENT:
            break;
        }
    }
    return MB_ENOERR;
}

在这里插入图片描述
上面的部分的讲解:
在这里插入图片描述
不加typedef :枚举类型的声明,后面省略了一个标识符,表示声明了一个省略标识符的枚举类型。 eMBException 表示声明的同时 定义了一个枚举变量可以等于枚举体内的任何成员。
加了typedef : eMBException就等价于这个省略的标识符的枚举类型的别名,注意此处是类型。 可以这样定义枚举变量 eMBException a;
②typedef eMBException( *pxMBFunctionHandler ) ( UCHAR pucFrame, USHORT * pusLength );
不加typedef :此函数是一个函数指针,返回eMBException枚举类型的值。入口参数是2个指针。pxMBFunctionHandler是函数标识符 pxMBFunctionHandler作为函数指针变量名(此处是函数指针变量) 加了typedef : pxMBFunctionHandler作为函数指针类型名, 相当于eMBException(
) ( UCHAR * , USHORT * );函数指针类型的别名。(此处是函数指针 类型)

详解

eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength );
其中的xFuncHandlers是一个结构体数组。

/* An array of Modbus functions handlers which associates Modbus function
 * codes with implementing functions.
 */
static xMBFunctionHandler xFuncHandlers[MB_FUNC_HANDLERS_MAX] = {
#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED > 0
    {MB_FUNC_OTHER_REPORT_SLAVEID, eMBFuncReportSlaveID},
#endif
#if MB_FUNC_READ_INPUT_ENABLED > 0
    {MB_FUNC_READ_INPUT_REGISTER, eMBFuncReadInputRegister},
#endif
#if MB_FUNC_READ_HOLDING_ENABLED > 0
    {MB_FUNC_READ_HOLDING_REGISTER, eMBFuncReadHoldingRegister},
#endif
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED > 0
    {MB_FUNC_WRITE_MULTIPLE_REGISTERS, eMBFuncWriteMultipleHoldingRegister},
#endif
#if MB_FUNC_WRITE_HOLDING_ENABLED > 0
    {MB_FUNC_WRITE_REGISTER, eMBFuncWriteHoldingRegister},
#endif
#if MB_FUNC_READWRITE_HOLDING_ENABLED > 0
    {MB_FUNC_READWRITE_MULTIPLE_REGISTERS, eMBFuncReadWriteMultipleHoldingRegister},
#endif
#if MB_FUNC_READ_COILS_ENABLED > 0
    {MB_FUNC_READ_COILS, eMBFuncReadCoils},
#endif
#if MB_FUNC_WRITE_COIL_ENABLED > 0
    {MB_FUNC_WRITE_SINGLE_COIL, eMBFuncWriteCoil},
#endif
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED > 0
    {MB_FUNC_WRITE_MULTIPLE_COILS, eMBFuncWriteMultipleCoils},
#endif
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED > 0
    {MB_FUNC_READ_DISCRETE_INPUTS, eMBFuncReadDiscreteInputs},
#endif
};

xFuncHandlers是一个结构体数组。其中包括这两个数据{A,B},A代表着功能码,B代表着有参可返回值为eMBException的函数。
因为此时的A就是ucFunctionCode功能吗,B是pxHandler,其中的pxHandler是带有pucFrame,和pusLength两个参数。
在这里插入图片描述
其中的A在xFuncHandlers中已经宏定义的功能码。
在这里插入图片描述
在上面的对数据包处理的函数的过程是比如先获取功能码16写入数据:先获取数据中的功能码,在和结构体数组中的xFuncHandlers[i].ucFunctionCode == ucFunctionCode,所匹配的数组中的功能码进行匹配如上面显示的,当匹配成功后,走到eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength );,
在这里插入图片描述
其中的ucMBFrame是指的是接受和发送报文的数据缓冲区,在这个中usLength 是数据长度大小,而这个pxHandler( ucMBFrame, &usLength ),代表的是其中的B,而在功能码16的B代表着eMBFuncWriteMultipleHoldingRegister这个函数

eMBException
eMBFuncWriteMultipleHoldingRegister( UCHAR * pucFrame, USHORT * usLen )
{
    USHORT          usRegAddress;
    USHORT          usRegCount;
    UCHAR           ucRegByteCount;

    eMBException    eStatus = MB_EX_NONE;
    eMBErrorCode    eRegStatus;

    if( *usLen >= ( MB_PDU_FUNC_WRITE_MUL_SIZE_MIN + MB_PDU_SIZE_MIN ) )
    {
        usRegAddress = ( USHORT )( pucFrame[MB_PDU_FUNC_WRITE_MUL_ADDR_OFF] << 8 );
        usRegAddress |= ( USHORT )( pucFrame[MB_PDU_FUNC_WRITE_MUL_ADDR_OFF + 1] );
        usRegAddress++;

        usRegCount = ( USHORT )( pucFrame[MB_PDU_FUNC_WRITE_MUL_REGCNT_OFF] << 8 );
        usRegCount |= ( USHORT )( pucFrame[MB_PDU_FUNC_WRITE_MUL_REGCNT_OFF + 1] );

        ucRegByteCount = pucFrame[MB_PDU_FUNC_WRITE_MUL_BYTECNT_OFF];

        if( ( usRegCount >= 1 ) &&
            ( usRegCount <= MB_PDU_FUNC_WRITE_MUL_REGCNT_MAX ) &&
            ( ucRegByteCount == ( UCHAR ) ( 2 * usRegCount ) ) )
        {
            /* Make callback to update the register values. */
            eRegStatus =
                eMBRegHoldingCB( &pucFrame[MB_PDU_FUNC_WRITE_MUL_VALUES_OFF],
                                 usRegAddress, usRegCount, MB_REG_WRITE );

            /* If an error occured convert it into a Modbus exception. */
            if( eRegStatus != MB_ENOERR )
            {
                eStatus = prveMBError2Exception( eRegStatus );
            }
            else
            {
                /* The response contains the function code, the starting
                 * address and the quantity of registers. We reuse the
                 * old values in the buffer because they are still valid.
                 */
                *usLen = MB_PDU_FUNC_WRITE_MUL_BYTECNT_OFF;
            }
        }
        else
        {
            eStatus = MB_EX_ILLEGAL_DATA_VALUE;
        }
    }
    else
    {
        /* Can't be a valid request because the length is incorrect. */
        eStatus = MB_EX_ILLEGAL_DATA_VALUE;
    }
    return eStatus;
}

其中就包含两个参数,并且这个函数调用的是eMBRegHoldingCB函数

eMBErrorCode eMBRegCoilsCB(UCHAR * pucRegBuffer, USHORT usAddress,
        USHORT usNCoils, eMBRegisterMode eMode)
{
    eMBErrorCode    eStatus = MB_ENOERR;
    USHORT          iRegIndex , iRegBitIndex , iNReg;
    UCHAR *         pucCoilBuf;
    USHORT          COIL_START;
    USHORT          COIL_NCOILS;
    USHORT          usCoilStart;
    iNReg =  usNCoils / 8 + 1;

    pucCoilBuf = ucSCoilBuf;
    COIL_START = S_COIL_START;
    COIL_NCOILS = S_COIL_NCOILS;
    usCoilStart = usSCoilStart;

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

    if( ( usAddress >= COIL_START ) &&
        ( usAddress + usNCoils <= COIL_START + COIL_NCOILS ) )
    {
        iRegIndex = (USHORT) (usAddress - usCoilStart) / 8;
        iRegBitIndex = (USHORT) (usAddress - usCoilStart) % 8;
        switch ( eMode )
        {
        /* read current coil values from the protocol stack. */
        case MB_REG_READ:
            while (iNReg > 0)
            {
                *pucRegBuffer++ = xMBUtilGetBits(&pucCoilBuf[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(&pucCoilBuf[iRegIndex++], iRegBitIndex, 8,
                        *pucRegBuffer++);
                iNReg--;
            }
            /* last coils */
            usNCoils = usNCoils % 8;
            /* xMBUtilSetBits has bug when ucNBits is zero */
            if (usNCoils != 0)
            {
                xMBUtilSetBits(&pucCoilBuf[iRegIndex++], iRegBitIndex, usNCoils,
                        *pucRegBuffer++);
            }
            break;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}

其中的usSRegHoldBuf[S_REG_HOLDING_NREGS]定义的宏定义也就是rt提供给直接用我们的数据。
在这里插入图片描述
所以最后的实现是落脚在eMBRegHoldingCB这个函数的。

在这里插入图片描述
在这里插入图片描述

在rt上的这个项目中在main中调用了start_machine函数,在这个函数中有调用了init_machine这个函数,又在这个函数中调用了eMBRegHoldingFunc函数使得set_funs头指针地址复制给psRegHoldFunc函数,psRegHoldFunc又在eMBRegHoldingCB这个函数中被回调,也就是相当于操作set_funs这个数组函数实现的回调过程。

在工作中用到的modebus是这样的是在user_mb_app.c中自己建立了一个函数为
在这里插入图片描述
在文件的上的又创建了一个枚举类型声明的一个函数数组。
在这里插入图片描述
在machine.c中有创见了一个init_machine函数,其中有一步就是调用embregholdingfunc函数进行枚举函数的调用。
在这里插入图片描述
在这里插入图片描述
set_test这是个函数。

在这里插入图片描述
其中的eMBRegHoldingFunc就是把set_funcs中的函数拷贝到pSRegHoldFunc[iRegIndex]这个里面,当我们在485上接受数据据的时候回调这个函数eMBRegHoldingCB,当收到不同的地址位的时候会调用不同的函数进行运作,会运行下面的代码进行调用相应的函数进行运行的 。
在这里插入图片描述

  • 7
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
//单片机Modbus RTU Slave程序 //==================================================================================================== //波 特 率:9600bps //起 始 位:1位 //数 据 位:8位 //校 验 位:偶校验 //停 止 位:1位 //系统时钟:11.0592MHz //物 理 层:RS485 //从站地址:0x01 //使用串口:STC12C5A60S2单片机串口1 //功 能 码:支持01、02、03、04、05、06、15、16等功能码 //01功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)数字量输出多路进行读操作 //02功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)数字量输入多路进行读操作 //03功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)模拟量输出多路进行读操作 //04功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)模拟量输入多路进行读操作 //05功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)数字量输出一路进行写操作 //06功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)模拟量输出一路进行写操作 //15功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)数字量输出多路进行写操作 //16功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)模拟量输出多路进行写操作

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值