diff --git a/Doxyfile b/Doxyfile index cee7d54..4ae5f82 100644 --- a/Doxyfile +++ b/Doxyfile @@ -32,7 +32,7 @@ PROJECT_NAME = SOEM # This could be handy for archiving the generated documentation or # if some version control system is used. -PROJECT_NUMBER = v1.3.3 +PROJECT_NUMBER = v1.4.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer diff --git a/doc/images/legacy_iomap.png b/doc/images/legacy_iomap.png new file mode 100644 index 0000000..1fa3914 Binary files /dev/null and b/doc/images/legacy_iomap.png differ diff --git a/doc/images/overlapping_iomap.png b/doc/images/overlapping_iomap.png new file mode 100644 index 0000000..b34e9cb Binary files /dev/null and b/doc/images/overlapping_iomap.png differ diff --git a/doc/soem.dox b/doc/soem.dox index 4785dc9..f03da0f 100644 --- a/doc/soem.dox +++ b/doc/soem.dox @@ -114,6 +114,13 @@ * - Error messages updated to latest ETG1020 document. * - FoE transfers now support busy response. * + * Features as of 1.4.0 : + * + * Added ERIKA target. + * Added macOS target. + * Support for EoE over existing mailbox API. + * + * * \section build Build instructions * * See README.md in the root folder. @@ -187,6 +194,9 @@ * - Added rtems target. * - Added support for overlapping IOmap. * + * Version 1.4.0 : 2019-05 + * - Various fixes and improvements + * * \section legal Legal notice * * Licensed under the GNU General Public License version 2 with exceptions. See diff --git a/doc/tutorial.txt b/doc/tutorial.txt index f722aa8..5f9a8ed 100644 --- a/doc/tutorial.txt +++ b/doc/tutorial.txt @@ -5,7 +5,7 @@ The SOEM is a library that provides the user application with the means to send and receive EtherCAT frames. It is up to the application to provide means for: - Reading and writing process data to be sent/received by SOEM - - Keeping local IO data synchronised with the global IOmap + - Keeping local IO data synchronized with the global IOmap - Detecting errors reported by SOEM - Managing errors reported by SOEM @@ -150,6 +150,122 @@ handling is split into ec_send_processdata and ec_receive_processdata. - Now we have a system up and running, all slaves are in state operational. +\section configuration_custom Custom Configuration + +\subsection iomap_config PDO Assign and PDO Config + +Do custom configuration with PDO Assign or PDO Config. SOEM support custom configuration during start via a +PreOP to SafeOP configuration hook. It can be done per slave and should be set before calling +the configuration and mapping of process data, e.g. the call to ec_config_map. Setting the configuration +hook ensure that the custom configuration will be applied when calling recover and re-configuration +of a slave, as described below. + +\code + +int EL7031setup(uint16 slave) +{ + int retval; + uint16 u16val; + + retval = 0; + + /* Map velocity PDO assignment via Complete Access*/ + uint16 map_1c12[4] = {0x0003, 0x1601, 0x1602, 0x1604}; + uint16 map_1c13[3] = {0x0002, 0x1a01, 0x1a03}; + + retval += ec_SDOwrite(slave, 0x1c12, 0x00, TRUE, sizeof(map_1c12), &map_1c12, EC_TIMEOUTSAFE); + retval += ec_SDOwrite(slave, 0x1c13, 0x00, TRUE, sizeof(map_1c13), &map_1c13, EC_TIMEOUTSAFE); + + /* set some motor parameters, just as example */ + u16val = 1200; // max motor current in mA + retval += ec_SDOwrite(slave, 0x8010, 0x01, FALSE, sizeof(u16val), &u16val, EC_TIMEOUTSAFE); + u16val = 150; // motor coil resistance in 0.01ohm + retval += ec_SDOwrite(slave, 0x8010, 0x04, FALSE, sizeof(u16val), &u16val, EC_TIMEOUTSAFE); + + /* set other necessary parameters as needed */ +... + printf("EL7031 slave %d set, retval = %d\n", slave, retval); + return 1; +} + +void simpletest(char *ifname) +{ +... + /* Detect slave beckhoff EL7031 from vendor ID and product code */ + if((ec_slave[slc].eep_man == 0x00000002) && (ec_slave[slc].eep_id == 0x1b773052)) + { + printf("Found %s at position %d\n", ec_slave[slc].name, slc); + /* link slave specific setup to preop->safeop hook */ + ec_slave[slc].PO2SOconfig = EL7031setup; + } +... +\endcode + +\subsection iomap_layout Legacy versus overlapping IOmap + +IOmap options legacy versus overlapping. Overlapping IOmap was introduced to handle +the TI ESC that doesn't support RW access to non-interleaved input and output process +data of multiple slaves. The difference is that legacy IOmapping will send IOmap as is +on the EtherCAT network while the overlapping will re-use logic addressing per slave to +replace RxPDO process data coming from the Master with TxPDO process data generated by the slave +sent back to the master. + +Overview of legacy pdo map +\image html legacy_iomap.png "Legacy IOmapping" +\image latex legacy_iomap.png "Legacy IOmapping" width=15cm + +Overview of overlapping pdo map +\image html overlapping_iomap.png "Overlapping IOmapping" +\image latex overlapping_iomap.png "Overlapping IOmapping" width=15cm + +\subsection iomap_groups EtherCAT slave groups + +Slave groups can be used to group slaves into separate logic groups within an EtherCAT network. +Each group will have its own logic address space mapped to an IOmap address and make it possible to +send and receive process data at different update rate. + +Below is an example on how to assign a slave to a group. OBS! A slave can only be member in one group. + +\code + for (cnt = 1; cnt <= ec_slavecount; cnt++) + { + if ( ) + { + ec_slave[cnt].group = ; + } + else + { + ec_slave[cnt].group = ; + } + } +\endcode + +Alternative 1, configure all slave groups at once, call ec_config_map or ec_config_map_group with arg 0. +This option will share IOmap and store the group IOmap data at offset EC_LOGGROUPOFFSET. + +\code + ec_config_map_group(&IOmap, 0); +\endcode + +Alternative 2, configure the slave groups one by one, call ec_config_map or ec_config_map_group with arg , . +This option will use different, supplied by the user, IOmaps. + +\code + ec_config_map_group(&IOmap1, ); + ec_config_map_group(&IOmap2, ); +\endcode + +To exchange process data for given group(s) the user must call send/recv process data per group. +The send and receive stack of process data don't consider groups, so the application has to send +and receive the process data for one group before sending/receiving process data for another group. + +\code + ec_send_processdata_group(); + ec_receive_processdata_group(, EC_TIMEOUTRET); + ec_send_processdata_group(); + ec_receive_processdata_group(, EC_TIMEOUTRET); +\endcode + \section application Application \subsection iomap Accessing data through IOmap diff --git a/osal/osal.h b/osal/osal.h index 6ff9eab..ed07c51 100644 --- a/osal/osal.h +++ b/osal/osal.h @@ -15,9 +15,13 @@ extern "C" #include /* General types */ -typedef uint8_t boolean; +#ifndef TRUE #define TRUE 1 +#endif +#ifndef FALSE #define FALSE 0 +#endif +typedef uint8_t boolean; typedef int8_t int8; typedef int16_t int16; typedef int32_t int32; diff --git a/osal/vxworks/osal.c b/osal/vxworks/osal.c index a9f7f84..cfb0d02 100644 --- a/osal/vxworks/osal.c +++ b/osal/vxworks/osal.c @@ -72,10 +72,14 @@ int osal_usleep (uint32 usec) int osal_gettimeofday(struct timeval *tv, struct timezone *tz) { + struct timespec ts; int return_value; + (void)tz; /* Not used */ - return_value = gettimeofday(tv, tz); - + /* Use clock_gettime CLOCK_MONOTONIC to a avoid NTP time adjustments */ + return_value = clock_gettime(CLOCK_MONOTONIC, &ts); + tv->tv_sec = ts.tv_sec; + tv->tv_usec = ts.tv_nsec / 1000; return return_value; } diff --git a/oshw/vxworks/nicdrv.c b/oshw/vxworks/nicdrv.c index e749023..0e45787 100644 --- a/oshw/vxworks/nicdrv.c +++ b/oshw/vxworks/nicdrv.c @@ -31,11 +31,14 @@ * This layer if fully transparent for the higher layers. */ +#include #include #include -#include -#include -#include +#include +#include +#include +#include +#include #include "oshw.h" #include "osal.h" @@ -96,6 +99,10 @@ const uint16 secMAC[3] = { 0x0404, 0x0404, 0x0404 }; /** second MAC word is used for identification */ #define RX_SEC secMAC[1] +/* usec per tick for timeconversion, default to 1kHz */ +#define USECS_PER_SEC 1000000 +static unsigned int usec_per_tick = 1000; + /** Receive hook called by Mux driver. */ static int mux_rx_callback(void* pCookie, long type, M_BLK_ID pMblk, LL_HDR_INFO *llHdrInfo, void *muxUserArg); @@ -126,6 +133,11 @@ int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary) int unit_no = -1; ETHERCAT_PKT_DEV * pPktDev; + /* Get systick info, sysClkRateGet return ticks per second */ + usec_per_tick = USECS_PER_SEC / sysClkRateGet(); + /* Don't allow 0 since it is used in DIV */ + if(usec_per_tick == 0) + usec_per_tick = 1; /* Make reference to packet device struct, keep track if the packet * device is the redundant or not. */ @@ -139,14 +151,14 @@ int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary) pPktDev = &(port->pktDev); pPktDev->redundant = 0; } - + /* Clear frame counters*/ pPktDev->tx_count = 0; pPktDev->rx_count = 0; pPktDev->overrun_count = 0; /* Create multi-thread support semaphores */ - port->sem_get_index = semBCreate(SEM_Q_FIFO, SEM_FULL); + port->sem_get_index = semMCreate(SEM_Q_PRIORITY | SEM_INVERSION_SAFE); /* Get the dev name and unit from ifname * We assume form gei1, fei0... @@ -162,23 +174,32 @@ int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary) break; } } - + /* Detach IP stack */ //ipDetach(pktDev.unit,pktDev.name); - + pPktDev->port = port; - + /* Bind to mux driver for given interface, include ethercat driver pointer * as user reference */ /* Bind to mux */ - pPktDev->pCookie = muxBind(ifn, unit_no, mux_rx_callback, NULL, NULL, NULL, MUX_PROTO_SNARF, "ECAT SNARF", pPktDev); + pPktDev->pCookie = muxBind(ifn, + unit_no, + mux_rx_callback, + NULL, + NULL, + NULL, + MUX_PROTO_SNARF, + "ECAT SNARF", + pPktDev); if (pPktDev->pCookie == NULL) { - /* fail */ - NIC_LOGMSG("ecx_setupnic: muxBind init for gei: %d failed\n", unit_no, 2, 3, 4, 5, 6); - goto exit; + /* fail */ + NIC_LOGMSG("ecx_setupnic: muxBind init for gei: %d failed\n", + unit_no, 2, 3, 4, 5, 6); + goto exit; } /* Get reference tp END obje */ @@ -186,11 +207,11 @@ int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary) if (port->pktDev.endObj == NULL) { - /* fail */ - NIC_LOGMSG("error_hook: endFindByName failed, device gei: %d not found\n", - unit_no, 2, 3, 4, 5, 6); - goto exit; - } + /* fail */ + NIC_LOGMSG("error_hook: endFindByName failed, device gei: %d not found\n", + unit_no, 2, 3, 4, 5, 6); + goto exit; + } if (secondary) { @@ -236,17 +257,17 @@ int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary) /* Create mailboxes for each potential EtherCAT frame index */ for (i = 0; i < EC_MAXBUF; i++) { - port->msgQId[i] = msgQCreate(1, sizeof(M_BLK_ID), MSG_Q_FIFO); - if (port->msgQId[i] == MSG_Q_ID_NULL) - { - NIC_LOGMSG("ecx_setupnic: Failed to create MsgQ[%d]", - i, 2, 3, 4, 5, 6); - goto exit; - } + port->msgQId[i] = msgQCreate(1, sizeof(M_BLK_ID), MSG_Q_FIFO); + if (port->msgQId[i] == MSG_Q_ID_NULL) + { + NIC_LOGMSG("ecx_setupnic: Failed to create MsgQ[%d]", + i, 2, 3, 4, 5, 6); + goto exit; + } } ecx_clear_rxbufstat(&(port->rxbufstat[0])); - } - + } + /* setup ethernet headers in tx buffers so we don't have to repeat it */ for (i = 0; i < EC_MAXBUF; i++) { @@ -254,9 +275,9 @@ int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary) port->rxbufstat[i] = EC_BUF_EMPTY; } ec_setupheader(&(port->txbuf2)); - + return 1; - + exit: return 0; @@ -271,20 +292,31 @@ int ecx_closenic(ecx_portt *port) { int i; ETHERCAT_PKT_DEV * pPktDev; + M_BLK_ID trash_can; pPktDev = &(port->pktDev); for (i = 0; i < EC_MAXBUF; i++) { - if (port->msgQId[i] != MSG_Q_ID_NULL) - { - msgQDelete(port->msgQId[i]); + if (port->msgQId[i] != MSG_Q_ID_NULL) + { + if (msgQReceive(port->msgQId[i], + (char *)&trash_can, + sizeof(M_BLK_ID), + NO_WAIT) != ERROR) + { + NIC_LOGMSG("ecx_closenic: idx %d MsgQ close\n", i, + 2, 3, 4, 5, 6); + /* Free resources */ + netMblkClChainFree(trash_can); + } + msgQDelete(port->msgQId[i]); } } if (pPktDev->pCookie != NULL) { - muxUnbind(pPktDev->pCookie, MUX_PROTO_SNARF, mux_rx_callback); + muxUnbind(pPktDev->pCookie, MUX_PROTO_SNARF, mux_rx_callback); } /* Clean redundant resources if available*/ @@ -295,6 +327,16 @@ int ecx_closenic(ecx_portt *port) { if (port->redport->msgQId[i] != MSG_Q_ID_NULL) { + if (msgQReceive(port->redport->msgQId[i], + (char *)&trash_can, + sizeof(M_BLK_ID), + NO_WAIT) != ERROR) + { + NIC_LOGMSG("ecx_closenic: idx %d MsgQ close\n", i, + 2, 3, 4, 5, 6); + /* Free resources */ + netMblkClChainFree(trash_can); + } msgQDelete(port->redport->msgQId[i]); } } @@ -333,8 +375,6 @@ int ecx_getindex(ecx_portt *port) { int idx; int cnt; - MSG_Q_ID msgQId; - M_BLK_ID trash_can; semTake(port->sem_get_index, WAIT_FOREVER); @@ -356,27 +396,6 @@ int ecx_getindex(ecx_portt *port) } } port->rxbufstat[idx] = EC_BUF_ALLOC; - - /* Clean up any abandoned frames */ - msgQId = port->msgQId[idx]; - if (msgQReceive(msgQId, (char *)&trash_can, sizeof(M_BLK_ID), NO_WAIT) == OK) - { - /* Free resources */ - netMblkClChainFree(trash_can); - } - - if (port->redstate != ECT_RED_NONE) - { - port->redport->rxbufstat[idx] = EC_BUF_ALLOC; - /* Clean up any abandoned frames */ - msgQId = port->redport->msgQId[idx]; - if (msgQReceive(msgQId, (char *)&trash_can, sizeof(M_BLK_ID), NO_WAIT) == OK) - { - /* Free resources */ - netMblkClChainFree(trash_can); - } - } - port->lastidx = idx; semGive(port->sem_get_index); @@ -398,35 +417,59 @@ void ecx_setbufstat(ecx_portt *port, int idx, int bufstat) /** Low level transmit buffer over mux layer 2 driver * -* @param[in] pPktDev = packet device to send buffer over +* @param[in] pPktDev = packet device to send buffer over +* @param[in] idx = index in tx buffer array * @param[in] buf = buff to send * @param[in] len = bytes to send * @return driver send result */ -static int ec_outfram_send(ETHERCAT_PKT_DEV * pPktDev, void * buf, int len) +static int ec_outfram_send(ETHERCAT_PKT_DEV * pPktDev, int idx, void * buf, int len) { - STATUS status = OK; - M_BLK_ID pPacket; - int rval = 0; + STATUS status = OK; + M_BLK_ID pPacket = NULL; + int rval = 0; + END_OBJ *endObj = (END_OBJ *)pPktDev->endObj; + MSG_Q_ID msgQId; - /* Allocate m_blk to send */ - if ((pPacket = netTupleGet(pPktDev->endObj->pNetPool, - len, - M_DONTWAIT, - MT_DATA, - TRUE)) == NULL) + /* Clean up any abandoned frames and re-use the allocated buffer*/ + msgQId = pPktDev->port->msgQId[idx]; + if(msgQNumMsgs(msgQId) > 0) { - NIC_LOGMSG("ec_outfram_send: Could not allocate MBLK memory!\n", 1, 2, 3, 4, 5, 6); - return ERROR; + pPktDev->abandoned_count++; + NIC_LOGMSG("ec_outfram_send: idx %d MsgQ abandoned\n", idx, + 2, 3, 4, 5, 6); + if (msgQReceive(msgQId, + (char *)&pPacket, + sizeof(M_BLK_ID), + NO_WAIT) == ERROR) + { + pPacket = NULL; + NIC_LOGMSG("ec_outfram_send: idx %d MsgQ mBlk handled by receiver\n", idx, + 2, 3, 4, 5, 6); + } + } + + if (pPacket == NULL) + { + /* Allocate m_blk to send */ + if ((pPacket = netTupleGet(endObj->pNetPool, + len, + M_DONTWAIT, + MT_DATA, + TRUE)) == NULL) + { + NIC_LOGMSG("ec_outfram_send: Could not allocate MBLK memory!\n", 1, 2, 3, 4, 5, 6); + return ERROR; + } } pPacket->mBlkHdr.mLen = len; - pPacket->mBlkHdr.mFlags |= M_HEADER; + pPacket->mBlkHdr.mFlags |= M_PKTHDR; pPacket->mBlkHdr.mData = pPacket->pClBlk->clNode.pClBuf; - pPacket->mBlkPktHdr.len = len; + pPacket->mBlkPktHdr.len = pPacket->m_len; netMblkFromBufCopy(pPacket, buf, 0, pPacket->mBlkHdr.mLen, M_DONTWAIT, NULL); - status = muxTkSend(pPktDev->pCookie, pPacket, NULL, htons(ETH_P_ECAT), NULL); + status = muxSend(endObj, pPacket); if (status == OK) { @@ -475,7 +518,7 @@ int ecx_outframe(ecx_portt *port, int idx, int stacknumber) } (*stack->rxbufstat)[idx] = EC_BUF_TX; - rval = ec_outfram_send(pPktDev, (char*)(*stack->txbuf)[idx], + rval = ec_outfram_send(pPktDev, idx, (char*)(*stack->txbuf)[idx], (*stack->txbuflength)[idx]); if (rval > 0) { @@ -516,12 +559,12 @@ int ecx_outframe_red(ecx_portt *port, int idx) ehp->sa1 = htons(secMAC[1]); /* transmit over secondary interface */ port->redport->rxbufstat[idx] = EC_BUF_TX; - rval = ec_outfram_send(&(port->redport->pktDev), &(port->txbuf2), port->txbuflength2); + rval = ec_outfram_send(&(port->redport->pktDev), idx, &(port->txbuf2), port->txbuflength2); if (rval <= 0) { port->redport->rxbufstat[idx] = EC_BUF_EMPTY; } - } + } return rval; } @@ -545,77 +588,85 @@ static int mux_rx_callback(void* pCookie, long type, M_BLK_ID pMblk, LL_HDR_INFO MSG_Q_ID msgQId; ETHERCAT_PKT_DEV * pPktDev; int length; + int bufstat; /* check if it is an EtherCAT frame */ if (type == ETH_P_ECAT) { - length = pMblk->mBlkHdr.mLen; - tempbuf = (ec_bufT *)pMblk->mBlkHdr.mData; - pPktDev = (ETHERCAT_PKT_DEV *)muxUserArg; - port = pPktDev->port; + length = pMblk->mBlkHdr.mLen; + tempbuf = (ec_bufT *)pMblk->mBlkHdr.mData; + pPktDev = (ETHERCAT_PKT_DEV *)muxUserArg; + port = pPktDev->port; - /* Get ethercat frame header */ - ecp = (ec_comt*)&(*tempbuf)[ETH_HEADERSIZE]; - idxf = ecp->index; - if (idxf >= EC_MAXBUF) - { - NIC_LOGMSG("mux_rx_callback: idx %d out of bounds\n", idxf, - 2, 3, 4, 5, 6); - return ret; - } + /* Get ethercat frame header */ + ecp = (ec_comt*)&(*tempbuf)[ETH_HEADERSIZE]; + idxf = ecp->index; + if (idxf >= EC_MAXBUF) + { + NIC_LOGMSG("mux_rx_callback: idx %d out of bounds\n", idxf, + 2, 3, 4, 5, 6); + return ret; + } - /* Check if it is the redundant port or not */ - if (pPktDev->redundant == 1) - { - msgQId = port->redport->msgQId[idxf]; - } - else - { - msgQId = port->msgQId[idxf]; - } + /* Check if it is the redundant port or not */ + if (pPktDev->redundant == 1) + { + bufstat = port->redport->rxbufstat[idxf]; + msgQId = port->redport->msgQId[idxf]; + } + else + { + bufstat = port->rxbufstat[idxf]; + msgQId = port->msgQId[idxf]; + } - if (length > 0) - { - /* Post the frame to the reqceive Q for the EtherCAT stack */ - STATUS status; - status = msgQSend(msgQId, (char *)&pMblk, sizeof(M_BLK_ID), - NO_WAIT, MSG_PRI_NORMAL); + /* Check length and if someone expects the frame */ + if (length > 0 && bufstat == EC_BUF_TX) + { + /* Post the frame to the receive Q for the EtherCAT stack */ + STATUS status; + status = msgQSend(msgQId, (char *)&pMblk, sizeof(M_BLK_ID), + NO_WAIT, MSG_PRI_NORMAL); + if (status == OK) + { + NIC_WVEVENT(ECAT_RECV_OK, (char *)&length, sizeof(length)); + ret = TRUE; + } + else if ((status == ERROR) && (errno == S_objLib_OBJ_UNAVAILABLE)) + { + /* Try to empty the MSGQ since we for some strange reason + * already have a frame in the MsqQ, + * is it due to timeout when receiving? + * We want the latest received frame in the buffer + */ + port->pktDev.overrun_count++; + NIC_LOGMSG("mux_rx_callback: idx %d MsgQ overrun\n", idxf, + 2, 3, 4, 5, 6); + M_BLK_ID trash_can; + if (msgQReceive(msgQId, + (char *)&trash_can, + sizeof(M_BLK_ID), + NO_WAIT) != ERROR) + { + /* Free resources */ + netMblkClChainFree(trash_can); + } + status = msgQSend(msgQId, + (char *)&pMblk, + sizeof(M_BLK_ID), + NO_WAIT, + MSG_PRI_NORMAL); if (status == OK) { - NIC_WVEVENT(ECAT_RECV_OK, (char *)&length, sizeof(length)); - ret = TRUE; + NIC_WVEVENT(ECAT_RECV_RETRY_OK, (char *)&length, sizeof(length)); + ret = TRUE; } - else if ((status == ERROR) && (errno == S_objLib_OBJ_UNAVAILABLE)) - { - /* Try to empty the MSGQ since we for some strange reason - * already have a frame in the MsqQ, - * is it due to timeout when receiving? - * We want the latest received frame in the buffer - */ - port->pktDev.overrun_count++; - NIC_LOGMSG("mux_rx_callback: idx %d MsgQ overrun\n", idxf, - 2, 3, 4, 5, 6); - M_BLK_ID trash_can; - if (msgQReceive(msgQId, (char *)&trash_can, - sizeof(M_BLK_ID), NO_WAIT) == OK) - { - /* Free resources */ - netMblkClChainFree(trash_can); - } - - status = msgQSend(msgQId, (char *)&pMblk, sizeof(M_BLK_ID), - NO_WAIT, MSG_PRI_NORMAL); - if (status == OK) - { - NIC_WVEVENT(ECAT_RECV_RETRY_OK, (char *)&length, sizeof(length)); - ret = TRUE; - } - } - else - { - NIC_WVEVENT(ECAT_RECV_FAILED, (char *)&length, sizeof(length)); - } - } + } + else + { + NIC_WVEVENT(ECAT_RECV_FAILED, (char *)&length, sizeof(length)); + } + } } return ret; @@ -631,7 +682,7 @@ static int ecx_recvpkt(ecx_portt *port, int idx, int stacknumber, M_BLK_ID * pMb { int bytesrx = 0; MSG_Q_ID msgQId; - int tick_timeout = max((timeout / 1000), 1); + int tick_timeout = max((timeout / usec_per_tick), 1); if (stacknumber == 1) { @@ -912,9 +963,9 @@ int ec_outframe_red(int idx) return ecx_outframe_red(&ecx_port, idx); } -int ec_inframe(int idx, int stacknumber) +int ec_inframe(int idx, int stacknumber, int timeout) { - return ecx_inframe(&ecx_port, idx, stacknumber); + return ecx_inframe(&ecx_port, idx, stacknumber, timeout); } int ec_waitinframe(int idx, int timeout) diff --git a/oshw/vxworks/nicdrv.h b/oshw/vxworks/nicdrv.h index 277d055..e009399 100644 --- a/oshw/vxworks/nicdrv.h +++ b/oshw/vxworks/nicdrv.h @@ -17,18 +17,18 @@ extern "C" #endif #include -#include /** structure to connect EtherCAT stack and VxWorks device */ typedef struct ETHERCAT_PKT_DEV { struct ecx_port *port; void *pCookie; - END_OBJ *endObj; + void *endObj; UINT32 redundant; UINT32 tx_count; UINT32 rx_count; UINT32 overrun_count; + UINT32 abandoned_count; }ETHERCAT_PKT_DEV; /** pointer structure to Tx and Rx stacks */ diff --git a/soem/ethercatconfig.c b/soem/ethercatconfig.c index 923ffbf..af667a6 100644 --- a/soem/ethercatconfig.c +++ b/soem/ethercatconfig.c @@ -142,7 +142,7 @@ int ecx_detect_slaves(ecx_contextt *context) { EC_PRINT("Error: too many slaves on network: num_slaves=%d, EC_MAXSLAVE=%d\n", wkc, EC_MAXSLAVE); - return -2; + return EC_SLAVECOUNTEXCEEDED; } } return wkc; @@ -589,8 +589,16 @@ int ecx_config_init(ecx_contextt *context, uint8 usetable) } /* some slaves need eeprom available to PDI in init->preop transition */ ecx_eeprom2pdi(context, slave); - /* request pre_op for slave */ - ecx_FPWRw(context->port, configadr, ECT_REG_ALCTL, htoes(EC_STATE_PRE_OP | EC_STATE_ACK) , EC_TIMEOUTRET3); /* set preop status */ + /* User may override automatic state change */ + if (context->manualstatechange == 0) + { + /* request pre_op for slave */ + ecx_FPWRw(context->port, + configadr, + ECT_REG_ALCTL, + htoes(EC_STATE_PRE_OP | EC_STATE_ACK), + EC_TIMEOUTRET3); /* set preop status */ + } } } return wkc; @@ -645,6 +653,10 @@ static int ecx_map_coe_soe(ecx_contextt *context, uint16 slave, int thread_n) { context->slavelist[slave].PO2SOconfig(slave); } + if (context->slavelist[slave].PO2SOconfigx) /* only if registered */ + { + context->slavelist[slave].PO2SOconfigx(context, slave); + } /* if slave not found in configlist find IO mapping in slave self */ if (!context->slavelist[slave].configindex) { @@ -1269,8 +1281,16 @@ int ecx_config_map_group(ecx_contextt *context, void *pIOmap, uint8 group) } ecx_eeprom2pdi(context, slave); /* set Eeprom control to PDI */ - ecx_FPWRw(context->port, configadr, ECT_REG_ALCTL, htoes(EC_STATE_SAFE_OP) , EC_TIMEOUTRET3); /* set safeop status */ - + /* User may override automatic state change */ + if (context->manualstatechange == 0) + { + /* request safe_op for slave */ + ecx_FPWRw(context->port, + configadr, + ECT_REG_ALCTL, + htoes(EC_STATE_SAFE_OP), + EC_TIMEOUTRET3); /* set safeop status */ + } if (context->slavelist[slave].blockLRW) { context->grouplist[group].blockLRW++; @@ -1405,8 +1425,16 @@ int ecx_config_overlap_map_group(ecx_contextt *context, void *pIOmap, uint8 grou } ecx_eeprom2pdi(context, slave); /* set Eeprom control to PDI */ - ecx_FPWRw(context->port, configadr, ECT_REG_ALCTL, htoes(EC_STATE_SAFE_OP), EC_TIMEOUTRET3); /* set safeop status */ - + /* User may override automatic state change */ + if (context->manualstatechange == 0) + { + /* request safe_op for slave */ + ecx_FPWRw(context->port, + configadr, + ECT_REG_ALCTL, + htoes(EC_STATE_SAFE_OP), + EC_TIMEOUTRET3); + } if (context->slavelist[slave].blockLRW) { context->grouplist[group].blockLRW++; diff --git a/soem/ethercatmain.c b/soem/ethercatmain.c index e91c7e8..30065b6 100644 --- a/soem/ethercatmain.c +++ b/soem/ethercatmain.c @@ -119,7 +119,8 @@ ecx_contextt ecx_context = { &ec_SM, // .eepSM = &ec_FMMU, // .eepFMMU = NULL, // .FOEhook() - NULL // .EOEhook() + NULL, // .EOEhook() + 0 // .manualstatechange }; #endif @@ -1097,7 +1098,8 @@ int ecx_mbxreceive(ecx_contextt *context, uint16 slave, ec_mbxbuft *mbx, int tim } else /* no read mailbox available */ { - wkc = 0; + if (wkc > 0) + wkc = EC_TIMEOUT; } } diff --git a/soem/ethercatmain.h b/soem/ethercatmain.h index 3c39674..7001572 100644 --- a/soem/ethercatmain.h +++ b/soem/ethercatmain.h @@ -100,6 +100,8 @@ PACKED_END #define EC_SMENABLEMASK 0xfffeffff +typedef struct ecx_context ecx_contextt; + /** for list of ethercat slaves detected */ typedef struct ec_slave { @@ -225,8 +227,10 @@ typedef struct ec_slave uint8 FMMUunused; /** Boolean for tracking whether the slave is (not) responding, not used/set by the SOEM library */ boolean islost; - /** registered configuration function PO->SO */ + /** registered configuration function PO->SO, (DEPRECATED)*/ int (*PO2SOconfig)(uint16 slave); + /** registered configuration function PO->SO */ + int (*PO2SOconfigx)(ecx_contextt * context, uint16 slave); /** readable name */ char name[EC_MAXNAME + 1]; } ec_slavet; @@ -377,7 +381,6 @@ typedef struct PACKED ec_PDOdesc PACKED_END /** Context structure , referenced by all ecx functions*/ -typedef struct ecx_context ecx_contextt; struct ecx_context { /** port reference, may include red_port */ @@ -424,6 +427,8 @@ struct ecx_context int (*FOEhook)(uint16 slave, int packetnumber, int datasize); /** registered EoE hook */ int (*EOEhook)(ecx_contextt * context, uint16 slave, void * eoembx); + /** flag to control legacy automatic state change or manual state change */ + int manualstatechange; }; #ifdef EC_VER1 diff --git a/soem/ethercatprint.c b/soem/ethercatprint.c index 071eb98..6e4f801 100644 --- a/soem/ethercatprint.c +++ b/soem/ethercatprint.c @@ -291,6 +291,63 @@ char* ec_mbxerror2string( uint16 errorcode) return (char *) ec_mbxerrorlist[i].errordescription; } +/** Convert an error to text string. + * + * @param[in] Ec = Struct describing the error. + * @return readable string + */ +char* ecx_err2string(const ec_errort Ec) +{ + char timestr[20]; + sprintf(timestr, "Time:%12.3f", Ec.Time.sec + (Ec.Time.usec / 1000000.0) ); + switch (Ec.Etype) + { + case EC_ERR_TYPE_SDO_ERROR: + { + sprintf(estring, "%s SDO slave:%d index:%4.4x.%2.2x error:%8.8x %s\n", + timestr, Ec.Slave, Ec.Index, Ec.SubIdx, (unsigned)Ec.AbortCode, ec_sdoerror2string(Ec.AbortCode)); + break; + } + case EC_ERR_TYPE_EMERGENCY: + { + sprintf(estring, "%s EMERGENCY slave:%d error:%4.4x\n", + timestr, Ec.Slave, Ec.ErrorCode); + break; + } + case EC_ERR_TYPE_PACKET_ERROR: + { + sprintf(estring, "%s PACKET slave:%d index:%4.4x.%2.2x error:%d\n", + timestr, Ec.Slave, Ec.Index, Ec.SubIdx, Ec.ErrorCode); + break; + } + case EC_ERR_TYPE_SDOINFO_ERROR: + { + sprintf(estring, "%s SDO slave:%d index:%4.4x.%2.2x error:%8.8x %s\n", + timestr, Ec.Slave, Ec.Index, Ec.SubIdx, (unsigned)Ec.AbortCode, ec_sdoerror2string(Ec.AbortCode)); + break; + } + case EC_ERR_TYPE_SOE_ERROR: + { + sprintf(estring, "%s SoE slave:%d IDN:%4.4x error:%4.4x %s\n", + timestr, Ec.Slave, Ec.Index, (unsigned)Ec.AbortCode, ec_soeerror2string(Ec.ErrorCode)); + break; + } + case EC_ERR_TYPE_MBX_ERROR: + { + sprintf(estring, "%s MBX slave:%d error:%4.4x %s\n", + timestr, Ec.Slave, Ec.ErrorCode, ec_mbxerror2string(Ec.ErrorCode)); + break; + } + default: + { + sprintf(estring, "%s error:%8.8x\n", + timestr, (unsigned)Ec.AbortCode); + break; + } + } + return (char*) estring; +} + /** Look up error in ec_errorlist and convert to text string. * * @param[in] context = context struct @@ -299,57 +356,10 @@ char* ec_mbxerror2string( uint16 errorcode) char* ecx_elist2string(ecx_contextt *context) { ec_errort Ec; - char timestr[20]; if (ecx_poperror(context, &Ec)) { - sprintf(timestr, "Time:%12.3f", Ec.Time.sec + (Ec.Time.usec / 1000000.0) ); - switch (Ec.Etype) - { - case EC_ERR_TYPE_SDO_ERROR: - { - sprintf(estring, "%s SDO slave:%d index:%4.4x.%2.2x error:%8.8x %s\n", - timestr, Ec.Slave, Ec.Index, Ec.SubIdx, (unsigned)Ec.AbortCode, ec_sdoerror2string(Ec.AbortCode)); - break; - } - case EC_ERR_TYPE_EMERGENCY: - { - sprintf(estring, "%s EMERGENCY slave:%d error:%4.4x\n", - timestr, Ec.Slave, Ec.ErrorCode); - break; - } - case EC_ERR_TYPE_PACKET_ERROR: - { - sprintf(estring, "%s PACKET slave:%d index:%4.4x.%2.2x error:%d\n", - timestr, Ec.Slave, Ec.Index, Ec.SubIdx, Ec.ErrorCode); - break; - } - case EC_ERR_TYPE_SDOINFO_ERROR: - { - sprintf(estring, "%s SDO slave:%d index:%4.4x.%2.2x error:%8.8x %s\n", - timestr, Ec.Slave, Ec.Index, Ec.SubIdx, (unsigned)Ec.AbortCode, ec_sdoerror2string(Ec.AbortCode)); - break; - } - case EC_ERR_TYPE_SOE_ERROR: - { - sprintf(estring, "%s SoE slave:%d IDN:%4.4x error:%4.4x %s\n", - timestr, Ec.Slave, Ec.Index, (unsigned)Ec.AbortCode, ec_soeerror2string(Ec.ErrorCode)); - break; - } - case EC_ERR_TYPE_MBX_ERROR: - { - sprintf(estring, "%s MBX slave:%d error:%4.4x %s\n", - timestr, Ec.Slave, Ec.ErrorCode, ec_mbxerror2string(Ec.ErrorCode)); - break; - } - default: - { - sprintf(estring, "%s error:%8.8x\n", - timestr, (unsigned)Ec.AbortCode); - break; - } - } - return (char*) estring; + return ecx_err2string(Ec); } else { diff --git a/soem/ethercatprint.h b/soem/ethercatprint.h index d862cb7..43acc2e 100644 --- a/soem/ethercatprint.h +++ b/soem/ethercatprint.h @@ -19,6 +19,8 @@ extern "C" char* ec_sdoerror2string( uint32 sdoerrorcode); char* ec_ALstatuscode2string( uint16 ALstatuscode); char* ec_soeerror2string( uint16 errorcode); +char* ec_mbxerror2string( uint16 errorcode); +char* ecx_err2string(const ec_errort Ec); char* ecx_elist2string(ecx_contextt *context); #ifdef EC_VER1 diff --git a/soem/ethercattype.h b/soem/ethercattype.h index 86df704..6b4d17c 100644 --- a/soem/ethercattype.h +++ b/soem/ethercattype.h @@ -23,23 +23,30 @@ extern "C" { #endif -/** define EC_VER1 if version 1 default context and functions are needed - * comment if application uses only ecx_ functions and own context */ -#define EC_VER1 - #include "osal.h" +/** define EC_VER1 if version 1 default context and functions are needed + * define EC_VER2 if application uses only ecx_ functions and own context */ +#if !defined(EC_VER1) && !defined(EC_VER2) +# define EC_VER1 +#endif + + /** Define little endian target by default if no endian is set */ #if !defined(EC_LITTLE_ENDIAN) && !defined(EC_BIG_ENDIAN) # define EC_LITTLE_ENDIAN #endif -/** return value general error */ -#define EC_ERROR -3 /** return value no frame returned */ -#define EC_NOFRAME -1 +#define EC_NOFRAME -1 /** return value unknown frame received */ -#define EC_OTHERFRAME -2 +#define EC_OTHERFRAME -2 +/** return value general error */ +#define EC_ERROR -3 +/** return value too many slaves */ +#define EC_SLAVECOUNTEXCEEDED -4 +/** return value request timeout */ +#define EC_TIMEOUT -5 /** maximum EtherCAT frame length in bytes */ #define EC_MAXECATFRAME 1518 /** maximum EtherCAT LRW frame length in bytes */ diff --git a/test/linux/eoe_test/eoe_test.c b/test/linux/eoe_test/eoe_test.c index 7a69d8f..98f8997 100644 --- a/test/linux/eoe_test/eoe_test.c +++ b/test/linux/eoe_test/eoe_test.c @@ -238,7 +238,7 @@ void teststarter(char *ifname) /* request OP state for all slaves */ ec_writestate(0); - chk = 40; + chk = 200; /* wait for all slaves to reach OP state */ do { diff --git a/test/linux/red_test/red_test.c b/test/linux/red_test/red_test.c index d42e20f..e8a601f 100644 --- a/test/linux/red_test/red_test.c +++ b/test/linux/red_test/red_test.c @@ -91,7 +91,7 @@ void redtest(char *ifname, char *ifname2) /* activate cyclic process data */ dorun = 1; /* wait for all slaves to reach OP state */ - ec_statecheck(0, EC_STATE_OPERATIONAL, EC_TIMEOUTSTATE); + ec_statecheck(0, EC_STATE_OPERATIONAL, 5 * EC_TIMEOUTSTATE); oloop = ec_slave[0].Obytes; if ((oloop == 0) && (ec_slave[0].Obits > 0)) oloop = 1; if (oloop > 8) oloop = 8; diff --git a/test/linux/simple_test/simple_test.c b/test/linux/simple_test/simple_test.c index 684ff52..a76353f 100644 --- a/test/linux/simple_test/simple_test.c +++ b/test/linux/simple_test/simple_test.c @@ -70,7 +70,7 @@ void simpletest(char *ifname) ec_receive_processdata(EC_TIMEOUTRET); /* request OP state for all slaves */ ec_writestate(0); - chk = 40; + chk = 200; /* wait for all slaves to reach OP state */ do { diff --git a/test/win32/red_test/red_test.c b/test/win32/red_test/red_test.c index 9adf373..4bf9705 100644 --- a/test/win32/red_test/red_test.c +++ b/test/win32/red_test/red_test.c @@ -81,7 +81,7 @@ void redtest(char *ifname, char *ifname2) /* request OP state for all slaves */ ec_writestate(0); /* wait for all slaves to reach OP state */ - ec_statecheck(0, EC_STATE_OPERATIONAL, EC_TIMEOUTSTATE); + ec_statecheck(0, EC_STATE_OPERATIONAL, 5 * EC_TIMEOUTSTATE); oloop = ec_slave[0].Obytes; if ((oloop == 0) && (ec_slave[0].Obits > 0)) oloop = 1; if (oloop > 8) oloop = 8; diff --git a/test/win32/simple_test/simple_test.c b/test/win32/simple_test/simple_test.c index 2e93c68..d38fbeb 100644 --- a/test/win32/simple_test/simple_test.c +++ b/test/win32/simple_test/simple_test.c @@ -186,7 +186,7 @@ void simpletest(char *ifname) /* request OP state for all slaves */ ec_writestate(0); - chk = 40; + chk = 200; /* wait for all slaves to reach OP state */ do {