【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负荷,并降低中断占用时间。

RT-Thread移植freemodbus从站的具体步骤如下: 1. 首先,需要在RT-Thread的开发环境中添加freemodbus的相关代码。可以通过在RT-Thread的packages目录下添加freemodbus的代码库来实现。具体的代码库可以在GitHub上找到\[1\]。 2. 在移植过程中,需要根据实际需求对freemodbus进行配置。配置参数位于FreeModbus\modbus\include\mbconfig.h文件中\[3\]。需要注意的是,从站模式支持Modbus RTUModbus ASCII和Modbus TCP三种模式,而主机模式目前只支持常用的Modbus RTU模式。 3. 在移植过程中,需要根据实际情况对从站地址、转换延时时间、命令响应超时时间以及从机数量等进行配置。需要注意的是,目前协议栈只支持从机地址连续,并且起始地址从1开始。 4. 在移植完成后,可以通过调用eMBMasterInit方法初始化Modbus主机协议栈,并在此过程中对相关硬件进行初始化。然后,通过调用eMBMasterEnable方法启动Modbus主机,并通过线程或定时器轮询调用eMBMasterPoll方法来实现命令的响应。 5. 在使用主机请求API方法时,可以设定一定的请求超时时间,并在方法有结果后获取最新的从机数据。 总结起来,移植freemodbus从站需要添加相关代码库,进行配置,并在初始化和启动过程中对硬件进行初始化。然后,通过轮询调用eMBMasterPoll方法来实现命令的响应,并通过API方法获取从机数据\[2\]。 #### 引用[.reference_title] - *1* *2* *3* [RT-Thread 之 移植 FreeModbus 协议栈( 同时支持主机和从机功能)](https://blog.csdn.net/hanhui22/article/details/108358924)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值