/* * This software is dual-licensed under GPLv3 and a commercial * license. See the file LICENSE.md distributed with this software for * full license information. */ /** \file * \brief * Servo over EtherCAT (SoE) Module. */ #include "soem/soem.h" #include #include "osal.h" #include "oshw.h" #define EC_SOE_MAX_DRIVES 8 /** SoE (Servo over EtherCAT) mailbox structure */ OSAL_PACKED_BEGIN typedef struct OSAL_PACKED { ec_mbxheadert MbxHeader; uint8 opCode : 3; uint8 incomplete : 1; uint8 error : 1; uint8 driveNo : 3; uint8 elementflags; union { uint16 idn; uint16 fragmentsleft; }; } ec_SoEt; OSAL_PACKED_END /** Report SoE error. * * @param[in] context context struct * @param[in] Slave Slave number * @param[in] idn IDN that generated error * @param[in] Error Error code, see EtherCAT documentation for list */ void ecx_SoEerror(ecx_contextt *context, uint16 Slave, uint16 idn, uint16 Error) { ec_errort Ec; memset(&Ec, 0, sizeof(Ec)); Ec.Time = osal_current_time(); Ec.Slave = Slave; Ec.Index = idn; Ec.SubIdx = 0; context->ecaterror = TRUE; Ec.Etype = EC_ERR_TYPE_SOE_ERROR; Ec.ErrorCode = Error; ecx_pusherror(context, &Ec); } /** SoE read, blocking. * * The IDN object of the selected slave and DriveNo is read. If a response * is larger than the mailbox size then the response is segmented. The function * will combine all segments and copy them to the parameter buffer. * * @param[in] context context struct * @param[in] slave Slave number * @param[in] driveNo Drive number in slave * @param[in] elementflags Flags to select what properties of IDN are to be transferred. * @param[in] idn IDN. * @param[in,out] psize Size in bytes of parameter buffer, returns bytes read from SoE. * @param[out] p Pointer to parameter buffer * @param[in] timeout Timeout in us, standard is EC_TIMEOUTRXM * @return Workcounter from last slave response */ int ecx_SoEread(ecx_contextt *context, uint16 slave, uint8 driveNo, uint8 elementflags, uint16 idn, int *psize, void *p, int timeout) { ec_SoEt *SoEp, *aSoEp; int totalsize, framedatasize; int wkc; uint8 *bp; uint8 *mp; uint16 *errorcode; ec_mbxbuft *MbxIn, *MbxOut; uint8 cnt; boolean NotLast; MbxIn = NULL; MbxOut = NULL; /* Empty slave out mailbox if something is in. Timeout set to 0 */ wkc = ecx_mbxreceive(context, slave, &MbxIn, 0); MbxOut = ecx_getmbx(context); ec_clearmbx(MbxOut); SoEp = (ec_SoEt *)MbxOut; SoEp->MbxHeader.length = htoes(sizeof(ec_SoEt) - sizeof(ec_mbxheadert)); SoEp->MbxHeader.address = htoes(0x0000); SoEp->MbxHeader.priority = 0x00; /* get new mailbox count value, used as session handle */ cnt = ec_nextmbxcnt(context->slavelist[slave].mbx_cnt); context->slavelist[slave].mbx_cnt = cnt; SoEp->MbxHeader.mbxtype = ECT_MBXT_SOE + MBX_HDR_SET_CNT(cnt); /* SoE */ SoEp->opCode = ECT_SOE_READREQ; SoEp->incomplete = 0; SoEp->error = 0; SoEp->driveNo = driveNo; SoEp->elementflags = elementflags; SoEp->idn = htoes(idn); totalsize = 0; bp = p; mp = (uint8 *)MbxIn + sizeof(ec_SoEt); NotLast = TRUE; /* send SoE request to slave */ wkc = ecx_mbxsend(context, slave, MbxOut, EC_TIMEOUTTXM); MbxOut = NULL; if (wkc > 0) /* succeeded to place mailbox in slave ? */ { while (NotLast) { if (MbxIn) ecx_dropmbx(context, MbxIn); MbxIn = NULL; /* read slave response */ wkc = ecx_mbxreceive(context, slave, &MbxIn, timeout); if (wkc > 0) /* succeeded to read slave response ? */ { aSoEp = (ec_SoEt *)MbxIn; /* slave response should be SoE, ReadRes */ if (((aSoEp->MbxHeader.mbxtype & 0x0f) == ECT_MBXT_SOE) && (aSoEp->opCode == ECT_SOE_READRES) && (aSoEp->error == 0) && (aSoEp->driveNo == driveNo) && (aSoEp->elementflags == elementflags)) { framedatasize = etohs(aSoEp->MbxHeader.length) - sizeof(ec_SoEt) + sizeof(ec_mbxheadert); totalsize += framedatasize; /* Does parameter fit in parameter buffer ? */ if (totalsize <= *psize) { /* copy parameter data in parameter buffer */ memcpy(bp, mp, framedatasize); /* increment buffer pointer */ bp += framedatasize; } else { framedatasize -= totalsize - *psize; totalsize = *psize; /* copy parameter data in parameter buffer */ if (framedatasize > 0) memcpy(bp, mp, framedatasize); } if (!aSoEp->incomplete) { NotLast = FALSE; *psize = totalsize; } } /* other slave response */ else { NotLast = FALSE; if (((aSoEp->MbxHeader.mbxtype & 0x0f) == ECT_MBXT_SOE) && (aSoEp->opCode == ECT_SOE_READRES) && (aSoEp->error == 1)) { mp = (uint8 *)MbxIn + (etohs(aSoEp->MbxHeader.length) + sizeof(ec_mbxheadert) - sizeof(uint16)); errorcode = (uint16 *)mp; ecx_SoEerror(context, slave, idn, *errorcode); } else { ecx_packeterror(context, slave, idn, 0, 1); /* Unexpected frame returned */ } wkc = 0; } } else { NotLast = FALSE; ecx_packeterror(context, slave, idn, 0, 4); /* no response */ } } } if (MbxIn) ecx_dropmbx(context, MbxIn); if (MbxOut) ecx_dropmbx(context, MbxOut); return wkc; } /** SoE write, blocking. * * The IDN object of the selected slave and DriveNo is written. If a response * is larger than the mailbox size then the response is segmented. * * @param[in] context context struct * @param[in] slave Slave number * @param[in] driveNo Drive number in slave * @param[in] elementflags Flags to select what properties of IDN are to be transferred. * @param[in] idn IDN. * @param[in] psize Size in bytes of parameter buffer. * @param[out] p Pointer to parameter buffer * @param[in] timeout Timeout in us, standard is EC_TIMEOUTRXM * @return Workcounter from last slave response */ int ecx_SoEwrite(ecx_contextt *context, uint16 slave, uint8 driveNo, uint8 elementflags, uint16 idn, int psize, void *p, int timeout) { ec_SoEt *SoEp, *aSoEp; int framedatasize, maxdata; int wkc; uint8 *mp; uint8 *hp; uint16 *errorcode; ec_mbxbuft *MbxIn, *MbxOut; uint8 cnt; boolean NotLast; MbxIn = NULL; MbxOut = NULL; /* Empty slave out mailbox if something is in. Timeout set to 0 */ wkc = ecx_mbxreceive(context, slave, &MbxIn, 0); hp = p; maxdata = context->slavelist[slave].mbx_l - sizeof(ec_SoEt); NotLast = TRUE; while (NotLast) { MbxOut = ecx_getmbx(context); ec_clearmbx(MbxOut); SoEp = (ec_SoEt *)MbxOut; SoEp->MbxHeader.address = htoes(0x0000); SoEp->MbxHeader.priority = 0x00; SoEp->opCode = ECT_SOE_WRITEREQ; SoEp->error = 0; SoEp->driveNo = driveNo; SoEp->elementflags = elementflags; mp = (uint8 *)MbxOut + sizeof(ec_SoEt); framedatasize = psize; NotLast = FALSE; SoEp->idn = htoes(idn); SoEp->incomplete = 0; if (framedatasize > maxdata) { framedatasize = maxdata; /* segmented transfer needed */ NotLast = TRUE; SoEp->incomplete = 1; SoEp->fragmentsleft = (uint16)(psize / maxdata); } SoEp->MbxHeader.length = htoes((uint16)(sizeof(ec_SoEt) - sizeof(ec_mbxheadert) + framedatasize)); /* get new mailbox counter, used for session handle */ cnt = ec_nextmbxcnt(context->slavelist[slave].mbx_cnt); context->slavelist[slave].mbx_cnt = cnt; SoEp->MbxHeader.mbxtype = ECT_MBXT_SOE + MBX_HDR_SET_CNT(cnt); /* SoE */ /* copy parameter data to mailbox */ memcpy(mp, hp, framedatasize); hp += framedatasize; psize -= framedatasize; /* send SoE request to slave */ wkc = ecx_mbxsend(context, slave, MbxOut, EC_TIMEOUTTXM); MbxOut = NULL; if (wkc > 0) /* succeeded to place mailbox in slave ? */ { if (!NotLast || !ecx_mbxempty(context, slave, timeout)) { if (MbxIn) ecx_dropmbx(context, MbxIn); MbxIn = NULL; /* read slave response */ wkc = ecx_mbxreceive(context, slave, &MbxIn, timeout); if (wkc > 0) /* succeeded to read slave response ? */ { aSoEp = (ec_SoEt *)MbxIn; NotLast = FALSE; /* slave response should be SoE, WriteRes */ if (((aSoEp->MbxHeader.mbxtype & 0x0f) == ECT_MBXT_SOE) && (aSoEp->opCode == ECT_SOE_WRITERES) && (aSoEp->error == 0) && (aSoEp->driveNo == driveNo) && (aSoEp->elementflags == elementflags)) { /* SoE write succeeded */ } /* other slave response */ else { if (((aSoEp->MbxHeader.mbxtype & 0x0f) == ECT_MBXT_SOE) && (aSoEp->opCode == ECT_SOE_READRES) && (aSoEp->error == 1)) { mp = (uint8 *)MbxIn + (etohs(aSoEp->MbxHeader.length) + sizeof(ec_mbxheadert) - sizeof(uint16)); errorcode = (uint16 *)mp; ecx_SoEerror(context, slave, idn, *errorcode); } else { ecx_packeterror(context, slave, idn, 0, 1); /* Unexpected frame returned */ } wkc = 0; } } else { ecx_packeterror(context, slave, idn, 0, 4); /* no response */ } } } } if (MbxIn) ecx_dropmbx(context, MbxIn); if (MbxOut) ecx_dropmbx(context, MbxOut); return wkc; } /** SoE read AT and MTD mapping. * * SoE has standard indexes defined for mapping. This function * tries to read them and collect a full input and output mapping size * of designated slave. * * @param[in] context context struct * @param[in] slave Slave number * @param[out] Osize Size in bits of output mapping (MTD) found * @param[out] Isize Size in bits of input mapping (AT) found * @return >0 if mapping successful. */ int ecx_readIDNmap(ecx_contextt *context, uint16 slave, uint32 *Osize, uint32 *Isize) { int retVal = 0; int wkc; int psize; uint8 driveNr; uint16 entries, itemcount; ec_SoEmappingt SoEmapping; ec_SoEattributet SoEattribute; *Isize = 0; *Osize = 0; for (driveNr = 0; driveNr < EC_SOE_MAX_DRIVES; driveNr++) { psize = sizeof(SoEmapping); /* read output mapping via SoE */ wkc = ecx_SoEread(context, slave, driveNr, EC_SOE_VALUE_B, EC_IDN_MDTCONFIG, &psize, &SoEmapping, EC_TIMEOUTRXM); if ((wkc > 0) && (psize >= 4) && ((entries = etohs(SoEmapping.currentlength) / 2) > 0) && (entries <= EC_SOE_MAXMAPPING)) { /* command word (uint16) is always mapped but not in list */ *Osize += 16; for (itemcount = 0; itemcount < entries; itemcount++) { psize = sizeof(SoEattribute); /* read attribute of each IDN in mapping list */ wkc = ecx_SoEread(context, slave, driveNr, EC_SOE_ATTRIBUTE_B, SoEmapping.idn[itemcount], &psize, &SoEattribute, EC_TIMEOUTRXM); if ((wkc > 0) && (!SoEattribute.list)) { /* length : 0 = 8bit, 1 = 16bit .... */ *Osize += (int)8 << SoEattribute.length; } } } psize = sizeof(SoEmapping); /* read input mapping via SoE */ wkc = ecx_SoEread(context, slave, driveNr, EC_SOE_VALUE_B, EC_IDN_ATCONFIG, &psize, &SoEmapping, EC_TIMEOUTRXM); if ((wkc > 0) && (psize >= 4) && ((entries = etohs(SoEmapping.currentlength) / 2) > 0) && (entries <= EC_SOE_MAXMAPPING)) { /* status word (uint16) is always mapped but not in list */ *Isize += 16; for (itemcount = 0; itemcount < entries; itemcount++) { psize = sizeof(SoEattribute); /* read attribute of each IDN in mapping list */ wkc = ecx_SoEread(context, slave, driveNr, EC_SOE_ATTRIBUTE_B, SoEmapping.idn[itemcount], &psize, &SoEattribute, EC_TIMEOUTRXM); if ((wkc > 0) && (!SoEattribute.list)) { /* length : 0 = 8bit, 1 = 16bit .... */ *Isize += (int)8 << SoEattribute.length; } } } } /* found some I/O bits ? */ if ((*Isize > 0) || (*Osize > 0)) { retVal = 1; } return retVal; }