FAQ

FAQ Entries

With V2 Serial API stack sometimes does not respond to requests

If the V2 serial api is used the stack receives chunks of data. This is in general problematic with MODBUS RTU if we want to adhere to the strict timeouts. The reason for this is that MODBUS RTU detects the end of the frame by a silence interval of 3.5 times the character interval or 2ms if the baud rate is > 19200 baud.
For example assume that the porting layer performs a read with 32 bytes and some timeout. This data is then passed to the stack. If data is only passed when the buffer is filled then the timeout MBS_SERIAL_APIV2_RTU_DYNAMIC_TIMEOUT_MS has to be set to a timeout larger then the time required for receiving the 32 bytes. If only a single master and slave exist on the network this is fine and the only drawback is, that the minimum response time is at least this timeout. Problems arise if more than one slave exists on the network. For example assume the following:

There are two possible solutions for this:

What is the best way to update registers within the software?

Depending if you are using an RTOS or not the question has different answers. Let us first threat the easier case where no RTOS is used at all.

Updating of registers without an RTOS:

It is important to know that the stack does all the frame reception and sending internally but all processing is done within the context of the caller of eMBSPoll. That is no call to any of the registers callbacks for the input (eMBSRegisterInputCB), holding(::eMBSRegisterHoldingCB), discrete(::eMBSRegisterDiscreteCB) or coil registers(::eMBSRegisterCoilCB) is made when the CPU is not within the function eMBSPoll. Therefore any access to the registers is safe and no special care must be taken. A typical approach is shown below where the array arusHoldingRegisters holds the holding registers mapped at address 0x100.

STATIC USHORT   usRegHoldingValue[3];

int
main( void )
{
    ...
    do
    {
        /* Poll the communication stack. */
        eMBSPoll( xMBSHdl );

        /* Update register values. */
        usRegHoldingValue[ 0 ] = usGetCurrentSensorValue(  );
        
        /* Read holding registers. */
        if( usRegHoldingValue[ 1 ] = 1 )
        {
            /* Value of holding register mapped at 0x0101 is now 1. */
            /* Turn on the control valve. */
            vSetValve( 1 );
        }
    }
    while( MB_ENOERR == eStatus );
    ...
}

eMBException
eMyRegHoldingCB( UBYTE * pubRegBuffer, USHORT usAddress, USHORT usNRegs, eMBSRegisterMode eRegMode )
{
    eMBException    eException = MB_PDU_EX_ILLEGAL_DATA_ADDRESS;
    STATIC const ULONG usRegsMappedAt = 0x0100;
    ULONG           usRegStart = usAddress;
    ULONG           usRegEnd = usAddress + usNRegs - 1;
    USHORT          usIndex;
    USHORT          usIndexEnd;

    if( ( usNRegs > 0 ) &&
        ( usRegStart >= usRegsMappedAt )
        && ( usRegEnd <= ( usRegsMappedAt + MB_UTILS_NARRSIZE( usRegHoldingValue ) ) ) )
    {
        usIndex = ( USHORT ) ( usRegStart - usRegsMappedAt );
        usIndexEnd = ( USHORT ) ( usRegEnd - usRegsMappedAt );
        switch ( eRegMode )
        {
        case MBS_REGISTER_WRITE:
            for( ; usIndex <= usIndexEnd; usIndex++ )
            {
                usRegHoldingValue[usIndex] = ( USHORT ) * pubRegBuffer++ << 8;
                usRegHoldingValue[usIndex] |= ( USHORT ) * pubRegBuffer++;
            }
            break;

        default:
        case MBS_REGISTER_READ:

            for( ; usIndex <= usIndexEnd; usIndex++ )
            {
                *pubRegBuffer++ = ( UBYTE ) ( usRegHoldingValue[usIndex] >> 8 );
                *pubRegBuffer++ = ( UBYTE ) ( usRegHoldingValue[usIndex] & 0xFF );
            }
            break;
        }
        eException = MB_PDU_EX_NONE;
    }
    return eException;
}

Updating of registers with an RTOS:

If an RTOS is used there are two different approaches. The first approach is the easiest one and works by locking the MODBUS stack by using the ::MBP_ENTER_CRITICAL_SECTION and ::MBP_EXIT_CRITICAL_SECTION macros. This is illustrated below.

Updating of registers with an RTOS and locking:

In this example we assume that the MODBUS stack runs in a separate task called vTaskA. The task which needs to access and update the MODBUS values is called vTaskB. If one would simply implement this without any locking data race conditions could occur. Therefore the access to the global variables is locked by calling the appropriate macros in the MODBUS callback function for the registers. If both task then try to access the variables simultaneously one is blocked until the other one has finished reading or updating the registers. This ensures consistency.

STATIC USHORT   usRegHoldingValue[3];

void
vTaskA( void )
{
    ...
    do
    {
        /* Poll the communication stack. */
        eMBSPoll( xMBSHdl );
    }
    while( MB_ENOERR == eStatus );
    ...
}

void
vTaskB( void )
{
    USHORT  usRegHoldingValueNew;
    BOOL    bTurnOnValve;
    do
    {
        ...
        /* Make the measurement first to not lock the stack for too long. */
        usRegHoldingValueNew = usGetCurrentSensorValue(  );
        MBP_ENTER_CRITICAL_SECTION(  );
        /* Update register values. */
        usRegHoldingValue[ 0 ] = usRegHoldingValueNew;
        
        /* Read holding registers. */
        if( usRegHoldingValue[ 1 ] = 1 )
        {
            /* Value of holding register mapped at 0x0101 is now 1. */
            /* Turn on the control valve. */
            bTurnOnValve = 1;
        }
        else
        {
            bTurnOnValve = 0;
        }
        MBP_EXIT_CRITICAL_SECTION(  );    
        if( bTurnOnValve )
        {
            vSetValve( 1 );
        }
    } while( TRUE );
    ...
}

eMBException
eMyRegHoldingCB( UBYTE * pubRegBuffer, USHORT usAddress, USHORT usNRegs, eMBSRegisterMode eRegMode )
{
    eMBException    eException = MB_PDU_EX_ILLEGAL_DATA_ADDRESS;
    STATIC const ULONG usRegsMappedAt = 0x0100;
    ULONG           usRegStart = usAddress;
    ULONG           usRegEnd = usAddress + usNRegs - 1;
    USHORT          usIndex;
    USHORT          usIndexEnd;

    MBP_ENTER_CRITICAL_SECTION(  );
    if( ( usNRegs > 0 ) &&
        ( usRegStart >= usRegsMappedAt )
        && ( usRegEnd <= ( usRegsMappedAt + MB_UTILS_NARRSIZE( usRegHoldingValue ) ) ) )
    {
        usIndex = ( USHORT ) ( usRegStart - usRegsMappedAt );
        usIndexEnd = ( USHORT ) ( usRegEnd - usRegsMappedAt );
        switch ( eRegMode )
        {
        case MBS_REGISTER_WRITE:
            for( ; usIndex <= usIndexEnd; usIndex++ )
            {
                usRegHoldingValue[usIndex] = ( USHORT ) * pubRegBuffer++ << 8;
                usRegHoldingValue[usIndex] |= ( USHORT ) * pubRegBuffer++;
            }
            break;

        default:
        case MBS_REGISTER_READ:

            for( ; usIndex <= usIndexEnd; usIndex++ )
            {
                *pubRegBuffer++ = ( UBYTE ) ( usRegHoldingValue[usIndex] >> 8 );
                *pubRegBuffer++ = ( UBYTE ) ( usRegHoldingValue[usIndex] & 0xFF );
            }
            break;
        }
        eException = MB_PDU_EX_NONE;
    }
    MBP_EXIT_CRITICAL_SECTION(  );    
    return eException;
}

Updating of registers with an RTOS and message queues:

If the RTOS has a kind of message queues available a more elegant way is also possible. In this case a special data structure should be created which is used for exchanging data between the tasks.
typedef struct
{
  USHORT usRegAddress;
  USHORT usRegValue;
} xMQueueRegType_t;
The MODBUS stack now check its read message queue after calling its poll loop and then updates the registers. Because it is outside of its poll loop (See eMBSPoll) no consistency problems can arrive. Reading from the message queue should be done with zero timeouts and after the update has been performed normal polling should continue. This would look similar to. Please note that the functions bGetMessageFromQueue and its syntax are dependent on the RTOS you are using and therefore no fully working example can be given here.

void 
vTaskA( void )
{
    xMQueueRegType_t xMsgRegs;
    do
    {
        /* Poll the communication stack. */
        eMBSPoll( xMBSHdl );
        /* Check if there are some messages in the message queue. */
        /* If yes process them and update the registers. */
        while( bGetMessageFromQueue( &xRegReadQueue, &xMsgRegs ) )
        {
            /* Now process xMsgRegs and update the global registers. */
        }
    }
    while( MB_ENOERR == eStatus );
}

For writing a message can be directly sent within the callback. For this case we would modify the eMyRegHoldingCB function as following. Please note that also here the function vSendMessageToQueue should be replaced with the correct function from your operating system.

eMBException
eMyRegHoldingCB( UBYTE * pubRegBuffer, USHORT usAddress, USHORT usNRegs, eMBSRegisterMode eRegMode )
{
    xMQueueRegType_t xMsgRegs;
    ...

    case MBS_REGISTER_WRITE:
        for( ; usIndex <= usIndexEnd; usIndex++ )
        {
            usRegHoldingValue[usIndex] = ( USHORT ) * pubRegBuffer++ << 8;
            usRegHoldingValue[usIndex] |= ( USHORT ) * pubRegBuffer++;
            
            /* Create a data structure and send it to the queue. */
            xMsgRegs.usRegAddress = usRegStart + usIndex;
            xMsgRegs.usRegValue = usRegHoldingValue[usIndex];           
            vSendMessageToQueue( &xRegWriteQueue );
        }
    ...
}

Can you tell me something about the design of the MODBUS stack?

The commercial MODBUS stack was designed one year after FreeMODBUS has been used by a wide range of customers. Limitations found in FreeMODBUS where that it was never meant to share code with the MODBUS master stack and that it was limited to a single instance. The new basic design of the stack is shown in the figure below. For every instance allocated an internal MODBUS handle is allocated. This keeps all the information for one complete MODBUS slave. Every MODBUS slave can have:

We can also see that the porting layers make use of a platform specific porting layer. This porting layer needs to be implemented once for your platform. The other information within the handles includes current state information, the slave address, an event object for signaling and the current active MODBUS frame if their is any.

figures-block-diagram-handle.png

MODBUS data structures

With V2 Serial API stack sometimes does not respond to requests

Typically the stack has two (or more) independent tasks. On task is the MODBUS stack by itself which always executes in the context of the application. This simplifies locking and consistency issues because the time when the MODBUS stack performs something with the data is known a priori. The other task is the porting layer. Please note that task is not fully correct because this can either been an interrupt handler on systems without an RTOS or a normal task within an operating system. A simplified state diagram of the stack is shown below.

figures-state-diagram.png

State diagram

Normally the stack waits within the WAITING state and waits for something interesting to happen by waiting on an event queue. Messages to the event queue are posted within the callbacks made by the porting layer. For example in the RTU/ASCII mode the serial porting layer is used. In the V1 API which is normally used on systems without an RTOS a callback is made to the stack whenever a new character is available. This character is then stored in an internal buffer and a timer is started and the call completes immediately. All callback functions have been kept very short because this makes processing very fast and therefore suitable for interrupt use. If no more characters are received within the timeout the stack will again get an callback from the timer module and will then post an internal event telling the main task to process this MODBUS frame. Again the reason is that processing the MODBUS frame within the callback is not possible. Now the main task awakes and will proceed to the EXECUTE state. In the executed state the frame is decoded and checked. If all tests pass callbacks are made to the application where the use must provide the actual register values. Then the stack proceeds to the SEND state. In the send state the response is assembled and passed to the frame implementation layers (RTU/ASCII or TCP). They now start to transmit the data using the porting layer. After transmission the stack goes back to the WAITING state. The ERROR state is reached when there are errors within the porting layer, for example if a serial interface is no longer available and the stack can not continue.

(C) 2007 Embedded Solutions. Last updated on 27 Aug 2016.