...
Following table shows the basic structure of the SPI frame.
Range | Length | Description |
---|---|---|
0 .. 3 | 4 byte | MCTC SPI |
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 | ||
---|---|---|
| ||
/**< 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 | ||
---|---|---|
| ||
/****************************************************************************/ /** 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 |
...
|
For GOAL based applications this can be determined in the function goal_miMctcSpiProcessTask within the goal_mi_mctc_spi.c
module:
Code Block | ||
---|---|---|
| ||
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 |
Following code shows the state machine in exceprts:
Code Block | ||
---|---|---|
| ||
/****************************************************************************/ /** 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 | ||
---|---|---|
| ||
/* 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 | ||
---|---|---|
| ||
#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 | ||
---|---|---|
| ||
#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:
stop the application (on the application controller)
reset the SoM module (e.g. using the reset button on the arduino shield)
start debugging of the application (application controller)
run the application
Then the application should be able to start the communication module as required.