cancel
Showing results for 
Search instead for 
Did you mean: 

About communicate between STM32F4xx and modbus RTU ( hmi weintek )

TPhan
Associate II

I have some problem about this issue, i try a lot of to fix it, but it can not run like i expected.

If all words i post make anybody difficulty understand , i so sorry a lot.

My problem is:

Fisrt , i enable interrupt USART_IT_RXNE,

Second, in my ISR, i check state frame in modbus, if it ok, i will disable interrupt USART_IT_RXNE . Then i enable interrupt USART_FLAG_IDLE and enable DMA RX ( in order to start receive modbus frame from modbus rtu ).

Third, if UART IDLE will occur interrupt or DMA RX complete receive from modbus, i will disable interrupt USART IDLE and enable interrupt USART IT RXNE and re-config mode DMA again to prepare receive next time.

But it can not run, HMI always respone PLC not respone.

So if anybody have idea, please help me.

I would like to thank and appreciate anyone see my this post.

Three file is my code ( portserial + mbrtu run ok without DMA)

portserial + mbrtu not run with DMA.

6 REPLIES 6
AvaTar
Lead

Difficult to understand what your problem is.

> Three file is my code ( portserial + mbrtu run ok without DMA) portserial + mbrtu not run with DMA.

I assume this means the interrupt-based variant is working, the DMA base variant does not.

I would generally avoid DMA for reception of variable-length frames.

Especially for a protocol like Modbus RTU, were a frame is terminated by a specific idle period (3,5 character times).

Jack Peacock_2
Senior III

It may be related to inter-character and inter-frame gap times. Modbus has specific timing in RTU mode based on baud rate.

Your IDLE timeout should be greater than the inter-character gap time, otherwise you'll time out in the middle of a frame. This assumes the device at the other end follows the RTU timing rules. PCs using a COM port have a difficult time with this since they can't prevent extra delays between characters, USB adapters are especially prone to this since a CDC block transfer may occur in the middle of a frame.

The IDLE timeout should be slightly less than the inter-frame gap. If too long you'll run into the next frame. In a half-duplex system this is less than a problem since the bus turnaround will usually exceed inter-frame gaps.

Older STM32s have a fixed IDLE timeout, which usually doesn't work with RTU mode. Newer STM32s have better Modbus support including variable IDLE timeout.

If you enable IDLE before the first character arrives how do you prevent an immediate timeout if the other side doesn't start sending within the frame gap time? You might try a strategy where you wait with only an RXNE interrupt for the first character, and then enable IDLE and DMA (and disable RXNE) for the rest of the frame.

Jack Peacock

Thanks a lot of your question. Then, What should i resolve this problem?

Because when i receive data from usart modbus rtu, it will take a lot of time for interrupt usart.

Example, i have two task, one task calculation vehicle, one task read data from buffer. But a lot of time CPU execute interrupt usart.

No reason you can't use RX DMA with a variable length frame. When the IDLE interrupt occurs terminate the DMA transfer. The DMA counter tells you the size of the incoming frame.

For older STM32s you'll need some hardware support since the IDLE timeout is fixed too low to use with Modbus. Connect the the RXD line to a timer input pin as well as the USART. Reset the timer on pin transitions (i.e. incoming bit stream). When incoming stops, the timer will time out, the event for an end of frame.

Jack Peacock

AvaTar
Lead

I agree with Jack.

Use a separate timer for "older" MCUs, including the F407. Or use a newer one, which has UART units with programmable idle timeout. I had been working with a newer F0x, also the F30x has this feature.

The efficiency of DMA vs. interrupts increases with baudrate, relatively speaking. Modbus RTU is limited to 38400bps (officially).

> The IDLE timeout should be slightly less than the inter-frame gap. If too long you'll run into the next frame.

The Modbus documentation/specs. contain suggested settings (tolerances) for both inter-character and inter-frame timeout.

TPhan
Associate II

I agree with both of you about time gap, but in modbus-rtu library in github, every receive data, it will check event if State_RX_INIT or ILD or RCV:

here is code:

BOOL

xMBRTUReceiveFSM( void )

{

  BOOL      xTaskNeedSwitch = FALSE;

  UCHAR      ucByte;

  assert( eSndState == STATE_TX_IDLE );

  ( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte );

  switch ( eRcvState )

  {

    /* If we have received a character in the init state we have to

     * wait until the frame is finished.

     */

  case STATE_RX_INIT:

    vMBPortTimersEnable( );

    break;

    /* In the error state we wait until all characters in the

     * damaged frame are transmitted.

     */

  case STATE_RX_ERROR:

    vMBPortTimersEnable( );

    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.

     */

  case STATE_RX_IDLE:

    // usRcvBufferPos = 0;

    // ucRTUBuf[usRcvBufferPos++] = ucByte;

     eRcvState = STATE_RX_RCV;

     USART_ITConfig(UART_PORT, USART_IT_RXNE, DISABLE);

     USART_ClearFlag(USART2, USART_FLAG_IDLE);

     USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);

     DMA_Cmd(DMA1_Stream5, ENABLE);

     /* Enable t3.5 timers. */

     vMBPortTimersEnable( );

    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_RX_RCV:

    // USART_ITConfig(UART_PORT, USART_IT_RXNE, DISABLE);

    // USART_ClearFlag(USART2, USART_FLAG_IDLE);

    // USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);

    // DMA_Cmd(DMA1_Stream5, ENABLE);

    // if( usRcvBufferPos < MB_SER_PDU_SIZE_MAX )

    // {

    //   ucRTUBuf[usRcvBufferPos++] = ucByte;

    // }

    // else

    // {

    //   eRcvState = STATE_RX_ERROR;

    // }

    vMBPortTimersEnable( );

    break;

  }

  return xTaskNeedSwitch;

}

It have check time out for inter-character and inter-frame. i just modify if event state_rx_idle ( in that case : uart receive data) then i disable interrupt rx, enable interrupt idle and DMA.

i think it is ok. Have i make a mistake or my knowledge about modbus is not enough ?

i am a student ( third-year) .

Thanks so much your help.