【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 ) { BOOLxTaskNeedSwitch = FALSE; UCHARucByte; 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; /* 以下代码省略... */} } }

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

    推荐阅读