【STM32】FreeModbus-RTU主机模式下数据接受函数传递

前言

最近在使用RTT提供的FreeModbus软件包进行开发,由于想使用DMA进行数据传输,于是对接收部分函数进行了探究,写下此文章。如何实现DMA方式收发将会写在另一篇文章中。

环境

  1. 芯片:STM32L1x系列芯片
  2. 配置工具:CubeMX
  3. RT-thread版本:3.1.3
  4. HAL库版本:1.9.0
  5. IDE:KEIL v5.28

并没有使用官方推荐的Env工具进行配置,并且重写串口收发方式为硬件方式

FreeModbus接收函数传递

当串口出现接收中断后,随后调用MB的写好的一个函数prvvUARTRxISR()

/**
 * This function is serial receive callback function
*/
void USARTx_IRQHandler(void)
{
    /* 接收中断处理 */
    if(__HAL_UART_GET_FLAG(&huartx, UART_FLAG_RXNE))
    {
        __HAL_UART_CLEAR_FLAG(&huartx, UART_FLAG_RXNE);
        prvvUARTRxISR(); /* MB回调函数 */
    }
    /* 省略部分代码... */
}

在该函数中又会继续调用另一个函数pxMBMasterFrameCBByteReceived()
其中对于该函数的注释意思大致是:创建一个接收中断处理程序。然后这个函数将会调用pxMBFramecBByteReceived()。协议栈将调用xMBPortSerialGetBvte()来接收字符串。

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

通过使用在文件中搜索功能,找到pxMBFramecBByteReceived()的定义
这个其实是一个函数指针,在MB初始化的时候指向了另一个函数xMBMasterRTUReceiveFSM()

BOOL( *pxMBMasterFrameCBByteReceived )( void );
eMBMasterInit( eMBMode eMode, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
{
	/* 省略部分代码... */
	switch (eMode)
 	{
	    case MB_RTU:
	    /* 省略部分代码... */
		    pxMBMasterFrameCBByteReceived = xMBMasterRTUReceiveFSM;		
	    /* 省略部分代码... */
	    break
	/* 省略部分代码... */

}

找到这个函数,便是MB做接收处理函数

  1. 先调用xMBMasterPortSerialGetByte()函数接收储存在USART的数据寄存器的一个字节的数据
  2. 用switch判断当前状态,如果是在正常运行阶段,应该是处于STATE_M_RX_IDLE接收空闲状态。当第一个字节数据到达的时候,状态改变为STATE_M_RX_RCV正在接收状态,同时启动1.5ms和3.5ms的定时器。当定时器超时的时候,会重新变回接收空闲状态。
BOOL xMBMasterRTUReceiveFSM( void )
{
    BOOL            xTaskNeedSwitch = FALSE;
    UCHAR           ucByte;

    RT_ASSERT(( eSndState == STATE_M_TX_IDLE ) || ( eSndState == STATE_M_TX_XFWR ));

    /* 调用串口接收函数 */
    ( void )xMBMasterPortSerialGetByte( ( CHAR * ) & ucByte );

    switch ( eRcvState )
    {
    case STATE_M_RX_INIT:
        vMBMasterPortTimersT35Enable( );
        break;
    case STATE_M_RX_ERROR:
        vMBMasterPortTimersT35Enable( );
        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 and disable early
         * the timer of respond timeout .
         */
    case STATE_M_RX_IDLE:
    	/* In time of respond timeout,the receiver receive a frame.
    	 * Disable timer of respond timeout and change the transmiter state to idle.
    	 */
    	vMBMasterPortTimersDisable( );
    	eSndState = STATE_M_TX_IDLE;

        usMasterRcvBufferPos = 0;
        ucMasterRTURcvBuf[usMasterRcvBufferPos++] = ucByte;
        eRcvState = STATE_M_RX_RCV;

        /* Enable t3.5 timers. */
        vMBMasterPortTimersT35Enable( );
        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_M_RX_RCV:
        if( usMasterRcvBufferPos < MB_SER_PDU_SIZE_MAX )
        {
            ucMasterRTURcvBuf[usMasterRcvBufferPos++] = ucByte;
        }
        else
        {
            eRcvState = STATE_M_RX_ERROR;
        }
        vMBMasterPortTimersT35Enable();
        break;
    }
    return xTaskNeedSwitch;
}

查找定时器初始化函数就可以看到,定时器超时回调函数timer_timeout_ind()

BOOL xMBMasterPortTimersInit(USHORT usTimeOut50us)
{
    /* backup T35 ticks */
    usT35TimeOut50us = usTimeOut50us;

    rt_timer_init(&timer, "master timer",
                   timer_timeout_ind, /* 超时回调函数 */
                   RT_NULL,
                   (50 * usT35TimeOut50us) / (1000 * 1000 / RT_TICK_PER_SECOND) + 1,
                   RT_TIMER_FLAG_ONE_SHOT); /* one shot */

    return TRUE;
}

找到timer_timeout_ind()后发现,这简直是一个循环嵌套啊。
timer_timeout_ind()调用prvvTIMERExpiredISR()
prvvTIMERExpiredISR()再调用pxMBMasterPortCBTimerExpired()
pxMBMasterPortCBTimerExpired()是一个函数指针,在MB初始化的时候指向xMBMasterRTUTimerExpired()

最终在这个函数中,经过判断确认处于STATE_M_RX_RCV正在接收状态,通过xMBMasterPortEventPost发出帧接收完成信号EV_MASTER_FRAME_RECEIVED,并且重置接收状态为STATE_M_RX_IDLE接收空闲状态和失能定时器

BOOL xMBMasterRTUTimerExpired(void)
{
	BOOL xNeedPoll = FALSE;

	switch (eRcvState)
	{
	/* 省略部分代码... */

	case STATE_M_RX_RCV:
		xNeedPoll = xMBMasterPortEventPost(EV_MASTER_FRAME_RECEIVED);
		break;

	/* 省略部分代码... */
	}
	eRcvState = STATE_M_RX_IDLE;

	/* 省略部分代码... */

	vMBMasterPortTimersDisable( );

	/* 省略部分代码... */
}

那么这个信号最终在eMBMasterPoll()中得到响应,完成最终的接收和解码。

eMBErrorCode eMBMasterPoll(void)
{
    /* 省略部分代码... */

    if( xMBMasterPortEventGet( &eEvent ) == TRUE )
    {
        switch ( eEvent )
        {
        /* 省略部分代码... */

        case EV_MASTER_FRAME_RECEIVED:
            eStatus = peMBMasterFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );
            /* Check if the frame is for us. If not ,send an error process event. */
            if ( ( eStatus == MB_ENOERR ) && ( ucRcvAddress == ucMBMasterGetDestAddress() ) )
            {
                ( void ) xMBMasterPortEventPost( EV_MASTER_EXECUTE );
            }
            else
            {
                vMBMasterSetErrorType(EV_ERROR_RECEIVE_DATA);
                ( void ) xMBMasterPortEventPost( EV_MASTER_ERROR_PROCESS );
            }
            break;

        /* 以下代码省略... */

        }
    }
}

总结

这次查找其实废了挺大的工夫,主要是嵌套的函数实在是太多了。在后续的文章中使用32硬件自带的IDLE中断来代替这一系列繁琐的定时器流程,并且采用DMA传输的方式减轻CPU负荷,并降低中断占用时间。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Modbus-RTU是一种串行通信协议,常用于工业自动化领域。在STM32微控制器中,可以使用Modbus-RTU例程来实现Modbus通信。 首先,需要在STM32的开发环境中创建一个新的工程。接下来,需要导入一个支持Modbus-RTU的库文件,例如FreeModbus。然后,可以在工程中引入Modbus-RTU的头文件,并进行相应的配置。 在代码中,需要初始化串口以实现与Modbus设备的通信。可以使用STM32的UART模块来实现串口通信,并设置适当的波特率、数据位、校验位和停止位。然后,需要配置Modbus设备的地址,该地址用于唯一标识每个Modbus设备。 接下来,需要实现Modbus的处理函数。当Modbus主站发送请求时,STM32将读取请求并做出响应。可以使用相应的函数来读取和写入寄存器或线圈。同时,还需要处理异常情况,并发送合适的错误码。 最后,需要在主函数中初始化Modbus并启动主循环。主循环中可以不断接收并处理Modbus请求。 需要注意的是,对于不同的STM32型号和开发环境,可能会有不同的具体实现方法和配置步骤。因此,在实际开发中,可能需要参考具体的开发文档和示例代码来实现Modbus-RTUSTM32例程。 总结起来,通过使用Modbus-RTU例程,可以在STM32微控制器上实现Modbus通信。这种通信方式在工业自动化中广泛应用,可以实现设备之间的数据交换和控制操作。 ### 回答2: Modbus-RTU是一种串行通信协议,常用于工业自动化领域。STM32是一系列基于ARM Cortex-M内核的微控制器。modbus-rtu stm32例程指的是在STM32微控制器上实现Modbus-RTU协议的示例程序。 在STM32微控制器上实现Modbus-RTU协议,可以通过以下几个步骤来完成: 1. 首先,需要在STM32上配置串口通信。选择一个合适的USART(串口)通信接口,并设置波特率、数据位、停止位和校验位等参数,以满足Modbus-RTU协议的要求。 2. 接下来,需要编写程序来处理Modbus-RTU协议的数据传输。可以利用STM32的GPIO(通用输入输出)接口来连接Modbus-RTU所需要的引脚,例如DE/RE(使能/接收使能)引脚和RX/TX引脚。通过GPIO接口配置,可以实现数据的发送和接收功能。 3. 在程序中,需要实现Modbus-RTU协议的相关功能,例如数据的读取、写入、寄存器的读取和写入等。可以编写针对Modbus命令功能码的函数来实现这些功能。 4. 最后,通过调用上述函数以及适当的分析和处理,将Modbus-RTU数据进行解析和处理,并根据协议规定作出相应的响应。 通过上述步骤,在STM32微控制器上实现Modbus-RTU协议的示例程序就完成了。可以根据具体应用需求和硬件资源,对示例程序进行修改和优化,以实现更复杂的功能。同时,需要注意调试和测试过程中的错误排查和修复,以确保程序的正确性和稳定性。 ### 回答3: modbus-rtu是一种常用的串行通信协议,用于在工业控制领域中实现设备之间的通信。在stm32微控制器上实现modbus-rtu通信的例程,可以帮助我们快速理解并应用该协议。 首先,需要创建一个stm32工程,并配置相应的串口和GPIO引脚用于通信。然后,需导入modbus库,包括寄存器读写操作的函数。可以从网上下载并添加相应的库文件。 接下来,需要初始化串口,确定通信的波特率、数据位数、停止位数等参数,并设置stm32的USART寄存器为相应的模式。 然后,需要调用modbus库中提供的函数进行modbus的配置。例如,调用modbus_init_slave函数来初始化modbus从站,设置从站ID等参数。然后调用modbus_function_read_holding_registers和modbus_function_write_holding_registers函数来读写保持寄存器的数据。 在实际的代码中,还需要根据应用的需求,编写相应的逻辑和算法来处理modbus通信。例如,可以使用定时中断来定时发送和接收数据。在接收到数据后,可以使用modbus库中提供的函数来解析数据,并根据解析结果进行相应的控制和操作。 最后,进行编译、烧录和测试。通过串口调试助手等工具,可以观察和验证modbus通信的数据传输和功能是否正常。 总之,通过实现modbus-rtu stm32例程,我们可以学习和掌握在stm32微控制器上使用modbus通信的方法和技巧,为工业控制领域的应用提供基础和参考。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值