Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Typo

...

Following table shows the basic structure of the SPI frame.

Range

Length

Description

0 .. 3

4 byte

MCTC SPI

header

Header

4 .. 76

73 byte

cyclic data (data mapper)

77 .. 126

50 byte

RPC data

127

1 byte

reserved

The block MCTC SPI Header contains the following elements:

Code Block
languagec
/**< SPI cyclic data header */
typedef struct {
    uint16_t crc_le16;                          /**< checksum */
    uint8_t seq;                                /**< sequence number */
    uint8_t lenData;                            /**< data length */
} GOAL_MI_MCTC_SPI_CYCLIC_T;

The field crc_le16 contains a checksum (Fletcher-16, Wikipedia) with a starting value of 0x0007. The field seq contains a sequence counter which increases with each updated frame. The field lenData contains the length of valid data after the MCTC header (e.g. ..

With an attached Logic Analyzer, it is possible to check the header for valid values. The third value (0x89) is the incrementing sequence number and the fouth byte shows the actual length of data (0x7C or 124).

...

Is the application controller receiving valid frames?

...

  • The first 2 bytes (crc) contains a value != 0

  • The 3rd byte changes with each frame (cyclic counter)

  • The 4th byte (length) has a value != 07

Programmatically, valid frames are detected by checking the CRC field in the header.

For uGOAL this is determined within the SAPI module sapi.c:

Code Block
languagec
/****************************************************************************/
/** Basic cyclic communication function
 *
 * This function initiates SPI transfer and dispatches received data to
 * upper layers data callback and RPC handling.
 *
 */
void sapi_loop(
        void
)
{
    /* update frame header */
    sapi_pdUpdateHeader(sendData, GOAL_MI_MCTC_LEN - sizeof(GOAL_MI_MCTC_SPI_CYCLIC_T));

    /* transfer data via SPI */
    plat_spiTransfer((const char *) &sendData[0], (char *) receiveData, BUF_LENGTH);

    /* control LED if Connection valid or Wink active */
    if (true == sapi_pdCheckHeader(receiveData, GOAL_MI_MCTC_LEN - sizeof(GOAL_MI_MCTC_SPI_CYCLIC_T))) {

        /* inform application */
        som_dataCb(&receiveData[0], &sendData[0]);

        /* trigger RPC */
        goal_miMctcDmCbRead();
   }
}
Info

If line 22 within the function

...

sapi_loop is executed, valid frames from the communication module are received.

For GOAL based applications this can be determined in the function goal_miMctcSpiProcessTask within the goal_mi_mctc_spi.c module:

Code Block
languagec
        do {

            /* check frame CRC */
            res = goal_miMctcCrc(&pBufSpi[sizeof(GOAL_MI_MCTC_SPI_CYCLIC_T)], pHdr->lenData, GOAL_le16toh_p(&pHdr->crc_le16));
            if (GOAL_RES_ERR(res)) {
                break;
            }

            /* check sequence counter */
            if (pMiMctcSpi->seqCyclicRem != pHdr->seq) {

                /* inform MI MCTC that connection is running */
                goal_miMctcMonitorRx(pMiMctc, GOAL_TRUE);

                /* re-arm watchdog */
                pMiMctcSpi->tsTout = tsNow + pMiMctc->cfgToutValue;

                /* update seen sequence */
                pMiMctcSpi->seqCyclicRem = pHdr->seq;
            }

            if (pHdr->lenData) {
                /* process MI DM incoming data */
                goal_miDmReadSync(pMiMctcSpi->pMiDmRead, &pBufSpi[sizeof(GOAL_MI_MCTC_SPI_CYCLIC_T)]);
            }
        } while (0);
Info

If the checks shown in the code are performed successfully a valid frame was received.

Manually this could be checked by evaluating the crc from the SPI frame and calculating the expected value over the given data with the given length from the header. The header itself is skipped from crc calculation.

...

The RPC synchronisation follows a state machine with the following states:

State

Description

GOAL_MI_MCTC_RPC_STATE_INIT

Initial state

Entered initially or in error case

GOAL_MI_MCTC_RPC_STATE_SYNC_REQ

Initial request state

Entered after INIT state

GOAL_MI_MCTC_RPC_STATE_SYNC_ACK_LOCAL

Initial acknowledge state

Entered after SYNC REQ state

GOAL_MI_MCTC_RPC_STATE_SYNC_ACK_REMOTE

Initial sync request was acknowledged

Entered if SYNC request was acknowledged, the peer module received our request!

GOAL_MI_MCTC_RPC_STATE_RUN_ONCE

Setup state after Sync

Entered when SYNC was successful

GOAL_MI_MCTC_RPC_STATE_RUN

Normal operating state

Entered after SYNC is done

...

Info

If the state GOAL_MI_MCTC_RPC_STATE_SYNC_ACK_REMOTE is entered, the communication module received the frames sent by the application controller.

Following code shows the state machine in exceprts:

Code Block
languagec
/****************************************************************************/
/** MCTC MI - RPC Sync Loop
 */
void goal_miMctcRpcSyncLoop(
    MCTC_INST_T *pMiMctc                        /**< MI MCTC handle */
)
{
    GOAL_STATUS_T res = GOAL_OK;                /* result */
    GOAL_BOOL_T flgSetupLocal = GOAL_FALSE;     /* local setup done flag */
    GOAL_BOOL_T flgSetupRemote = GOAL_FALSE;    /* remote setup done flag */
    GOAL_MCTC_T *pHdlMctcTx = NULL;             /* call handle */

    /* leave if not in sync-mode */
    if ((GOAL_MI_MCTC_RPC_STATE_RUN == pMiMctc->stateRpc)
        || (GOAL_MI_MCTC_RPC_STATE_STOP == pMiMctc->stateRpc)) {
        return;
    }

    /* check if send is permitted */
    if (GOAL_TRUE != goal_miMctcRpcSendAllow(pMiMctc)) {
        return;
    }

    /* handle state machine */
    switch (pMiMctc->stateRpc) {

        case GOAL_MI_MCTC_RPC_STATE_RUN_ONCE:

            /* .... */
            
            break;

        /* initialize synchronisation */
        case GOAL_MI_MCTC_RPC_STATE_SYNC_INIT:

            goal_logDbg("state: SYNC_INIT");

            /* reset sync state */
            GOAL_MASK_SET(pMiMctc->flgRpc, GOAL_MI_MCTC_RPC_FLG_SYNC_REQ);
            GOAL_MASK_CLR(pMiMctc->flgRpc, GOAL_MI_MCTC_RPC_FLG_SYNC_ACK);
            pMiMctc->seqRpcLocal = 0;
            pMiMctc->seqRpcLocalAck = 0;
            pMiMctc->seqRpcRemoteAck = 0;
            pMiMctc->stateRpc = GOAL_MI_MCTC_RPC_STATE_SYNC_REQ;

            /* intended fallthrough */
            GOAL_TARGET_FALLTHROUGH;

        /* request synchronisation from partner */
        case GOAL_MI_MCTC_RPC_STATE_SYNC_REQ:

            goal_logDbg("state: SYNC_REQ");

            /* send RPC sync frame until ACK is received */
            if (!(GOAL_MI_MCTC_RPC_FLG_SYNC_ACK & pMiMctc->flgRpcRemote)) {

                /* send sync request frame */
                goal_miMctcRpcSend(pMiMctc, NULL, 0);

                break;
            }

            /* request acknowledge of first sequence */
            GOAL_MASK_CLR(pMiMctc->flgRpc, GOAL_MI_MCTC_RPC_FLG_SYNC_REQ);
            GOAL_MASK_SET(pMiMctc->flgRpc, GOAL_MI_MCTC_RPC_FLG_REQ_ACK);
            pMiMctc->seqRpcLocal = 1;
            pMiMctc->stateRpc = GOAL_MI_MCTC_RPC_STATE_SYNC_ACK_LOCAL;

            /* intended fallthrough */
            GOAL_TARGET_FALLTHROUGH;

        case GOAL_MI_MCTC_RPC_STATE_SYNC_ACK_LOCAL:

            goal_logDbg("state: SYNC_ACK_LOCAL");

            /* send empty sequence 1 until ACK is received and we confirmed remote sequence */
            if (1 != pMiMctc->seqRpcLocalAck) {

                /* send empty RPC request */
                goal_miMctcRpcSend(pMiMctc, NULL, 0);

                break;
            }

            /* wait for remote sync */
            pMiMctc->stateRpc = GOAL_MI_MCTC_RPC_STATE_SYNC_ACK_REMOTE;

            break;

        case GOAL_MI_MCTC_RPC_STATE_SYNC_ACK_REMOTE:

            goal_logDbg("state: SYNC_ACK_REMOTE");

            if (0 == pMiMctc->seqRpcRemoteAck) {

                /* send empty RPC request */
                goal_miMctcRpcSend(pMiMctc, NULL, 0);

                break;
            }

            /* enter run mode */
            GOAL_MASK_CLR(pMiMctc->flgRpc, GOAL_MI_MCTC_RPC_FLG_REQ_ACK);
            pMiMctc->stateRpc = GOAL_MI_MCTC_RPC_STATE_RUN_ONCE;

            break;
    }
}

Debugging

Disabling Peer-Loss Detection

By default both, the basic SPI communication and the RPC communication, are monitored and in case of loss of communication or a missing response an error is detected and propagated. This will stop the system from working any further. This can be disabled for debugging purpose. However this should never be done in a productive system, since this will mask potential communication problems.

For uGOAL application, the following defines need to be set in goal_config.h:

Code Block
languagec
/* for debugging purpose disable any timeout detection on the peer */
#define CONFIG_UGOAL_TIMEOUT_RPC 0
#define CONFIG_UGOAL_TIMEOUT_MEDIA 0

For GOAL based application, following code needs to be called before any other functions. So best place the following calls in appl_init()or using the make system, using the command in a project directory, like ugoal/projects/ugoal/02_profinet/gcc:

Code Block
languagec
#include <goal_media/goal_mi_mctc_spi.h>


/****************************************************************************/
/** Application Init
 *
 * Build up the device structure and initialize the Profinet stack.
 */
GOAL_STATUS_T appl_init(
    void
)
{
    GOAL_STATUS_T res = GOAL_OK;                /* result */

    /* disable media and RPC timeout */
    goal_miMctcCfgTout(0);
    goal_miMctcSpiCfgTout(0);

    /* initialize DD */
    res = goal_ddInit();
    if (GOAL_RES_ERR(res)) {
        goal_logErr("Initialization of DD failed");
    }

    /* ..... */
}

...

user: ~/.../02_profinet/gcc: make debug
removed directory: build/
 *** Debugging Mode enabled ***

and building the project:

Code Block
user: ~/ugoal/projects/ugoal/02_profinet/gcc: make PLATFORM=stm32f429zi

 *** Debugging Mode enabled - note that: ***
    > this mode should only be used for debugging purposes
    > peer-loss detection on application controller is disabled
    > logging might be enabled

 *** Disabling per 'make debug' ***

platform pre
[CC] plat/stm32/plat.c
...
Info

To switch the Debugging Mode off, use ‘make debug’ too.

For GOAL based application, following code needs to be called before any other functions. So best place the following calls in appl_init():

Code Block
languagec
#include <goal_media/goal_mi_mctc_spi.h>


/****************************************************************************/
/** Application Init
 *
 * Build up the device structure and initialize the Profinet stack.
 */
GOAL_STATUS_T appl_init(
    void
)
{
    GOAL_STATUS_T res = GOAL_OK;                /* result */

    /* disable media and RPC timeout */
    goal_miMctcCfgTout(0);
    goal_miMctcSpiCfgTout(0);

    /* initialize DD */
    res = goal_ddInit();
    if (GOAL_RES_ERR(res)) {
        goal_logErr("Initialization of DD failed");
    }

    /* ..... */
}

This will disable both the RPC timeout and the SPI communication timeout. If the application controller is halted for an arbitrary time, operation can be resumed without any error.

General Debugging procedure

If debugging an application some precautions need to be considered. The correct sequence for debugging a application would be:

  1. stop the application (on the application controller)

  2. reset the SoM module (e.g. using the reset button on the arduino shield)

  3. start debugging of the application (application controller)

  4. run the application

Then the application should be able to start the communication module as required.