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

你可能感兴趣的:(STM32)