/*
 * CANopen Receive Process Data Object protocol.
 *
 * @file        CO_PDO.c
 * @ingroup     CO_PDO
 * @author      Janez Paternoster
 * @copyright   2021 Janez Paternoster
 *
 * This file is part of <https://github.com/CANopenNode/CANopenNode>, a CANopen Stack.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 */

#include <string.h>

#include "301/CO_PDO.h"

#if ((CO_CONFIG_PDO) & (CO_CONFIG_RPDO_ENABLE | CO_CONFIG_TPDO_ENABLE)) != 0

#if ((CO_CONFIG_PDO)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_OD_IO_ACCESS) == 0
#error Dynamic PDO mapping is not possible without CO_CONFIG_PDO_OD_IO_ACCESS
#endif
#endif

#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_BITWISE_MAPPING) != 0
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_OD_IO_ACCESS) == 0
#error Bitwise PDO mapping is not possible without CO_CONFIG_PDO_OD_IO_ACCESS
#endif
#endif

#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_OD_IO_ACCESS) != 0
/*
 * Custom function for write dummy OD object. Will be used only from RPDO.
 *
 * For more information see file CO_ODinterface.h, OD_IO_t.
 */
static ODR_t
OD_write_dummy(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
    (void)stream;
    (void)buf;
    if (countWritten != NULL) {
        *countWritten = count;
    }
    return ODR_OK;
}

/*
 * Custom function for read dummy OD object. Will be used only from TPDO.
 *
 * For more information see file CO_ODinterface.h, OD_IO_t.
 */
static ODR_t
OD_read_dummy(OD_stream_t* stream, void* buf, OD_size_t count, OD_size_t* countRead) {
    if ((buf == NULL) || (stream == NULL) || (countRead == NULL)) {
        return ODR_DEV_INCOMPAT;
    }

    if (count > stream->dataLength) {
        count = stream->dataLength;
    }

    (void)memset(buf, 0, count);

    *countRead = count;
    return ODR_OK;
}

/*
 * Find mapped variable in Object Dictionary and configure entry in RPDO or TPDO
 *
 * @param PDO This object will be configured. If map is erroneous, then it will stay unchanged.
 * @param map PDO mapping parameter.
 * @param mapIndex from 0 to CO_PDO_MAX_MAPPED_ENTRIES
 * @param isRPDO True for RPDO and false for TPDO.
 * @param OD Object Dictionary.
 *
 * @return ODR_OK on success, otherwise error reason.
 */
static ODR_t
PDOconfigMap(CO_PDO_common_t* PDO, uint32_t map, uint8_t mapIndex, bool_t isRPDO, OD_t* OD) {
    uint16_t index = (uint16_t)(map >> 16);
    uint8_t subIndex = (uint8_t)(map >> 8);
    uint8_t mappedLengthBits = (uint8_t)map;
    uint8_t mappedLength = mappedLengthBits >> 3;
    OD_IO_t* OD_IO = &PDO->OD_IO[mapIndex];

    /* total PDO length can not be more than CO_PDO_MAX_SIZE bytes */
    if (mappedLength > CO_PDO_MAX_SIZE) {
        return ODR_MAP_LEN; /* PDO length exceeded */
    }

    /* is there a reference to the dummy entry */
    if ((index < 0x20U) && (subIndex == 0U)) {
        OD_stream_t* stream = &OD_IO->stream;
        (void)memset(stream, 0, sizeof(OD_stream_t));
        stream->dataLength = mappedLength;
        stream->dataOffset = mappedLength;
        OD_IO->read = OD_read_dummy;
        OD_IO->write = OD_write_dummy;
        return ODR_OK;
    }

    /* find entry in the Object Dictionary */
    OD_IO_t OD_IOcopy;
    OD_entry_t* entry = OD_find(OD, index);
    ODR_t odRet = OD_getSub(entry, subIndex, &OD_IOcopy, false);
    if (odRet != ODR_OK) {
        return odRet;
    }

    /* verify access attributes, byte alignment and length */
    OD_attr_t testAttribute = isRPDO ? (OD_attr_t)(ODA_RPDO) : (OD_attr_t)(ODA_TPDO);
#if ((CO_CONFIG_PDO) & (CO_CONFIG_PDO_BITWISE_MAPPING)) != 0
    /* If the bitwise mapping is enabled, check only length in bits */
    if (((OD_IOcopy.stream.attribute & testAttribute) == 0U)
        || ((OD_IOcopy.stream.dataLength * 8) < mappedLengthBits)) {
#else
    /* If the bitwise mapping is disabled, theck the alignment and length in bytes*/
    if (((OD_IOcopy.stream.attribute & testAttribute) == 0U) || ((mappedLengthBits & 0x07U) != 0U)
        || (OD_IOcopy.stream.dataLength < mappedLength)) {
#endif /* (CO_CONFIG_PDO) & (CO_CONFIG_PDO_BITWISE_MAPPING) */
        return ODR_NO_MAP; /* Object cannot be mapped to the PDO. */
    }

    /* Copy values and store mappedLength temporary. */
    *OD_IO = OD_IOcopy;
#if ((CO_CONFIG_PDO) & (CO_CONFIG_PDO_BITWISE_MAPPING)) != 0
    OD_IO->stream.dataOffset = mappedLengthBits;
#else
    OD_IO->stream.dataOffset = mappedLength;
#endif

    /* get TPDO request flag byte from extension */
#if OD_FLAGS_PDO_SIZE > 0
    if (!isRPDO) {
        if ((subIndex < (OD_FLAGS_PDO_SIZE * 8U)) && (entry->extension != NULL)) {
            PDO->flagPDObyte[mapIndex] = &entry->extension->flagsPDO[subIndex >> 3];
            PDO->flagPDObitmask[mapIndex] = 1U << (subIndex & 0x07U);
        } else {
            PDO->flagPDObyte[mapIndex] = NULL;
        }
    }
#endif

    return ODR_OK;
}

/*
 * Initialize PDO mapping parameters
 *
 * @param PDO This object.
 * @param OD Object Dictionary.
 * @param OD_PDOMapPar OD entry for "PDO mapping parameter".
 * @param isRPDO True for RPDO and false for TPDO.
 * @param [out] errInfo Additional information in case of error, may be NULL.
 * @param [out] erroneousMap Additional information about erroneous map.
 *
 * @return #CO_ReturnError_t CO_ERROR_NO on success.
 */
static CO_ReturnError_t
PDO_initMapping(CO_PDO_common_t* PDO, OD_t* OD, OD_entry_t* OD_PDOMapPar, bool_t isRPDO, uint32_t* errInfo,
                uint32_t* erroneousMap) {
    ODR_t odRet;
    size_t pdoDataLength = 0;
    uint8_t mappedObjectsCount = 0;

    /* number of mapped application objects in PDO */
    odRet = OD_get_u8(OD_PDOMapPar, 0, &mappedObjectsCount, true);
    if (odRet != ODR_OK) {
        if (errInfo != NULL) {
            *errInfo = ((uint32_t)OD_getIndex(OD_PDOMapPar)) << 8;
        }
        return CO_ERROR_OD_PARAMETERS;
    }

    for (uint8_t i = 0; i < CO_PDO_MAX_MAPPED_ENTRIES; i++) {
        OD_IO_t* OD_IO = &PDO->OD_IO[i];
        uint32_t map = 0;

        odRet = OD_get_u32(OD_PDOMapPar, i + 1U, &map, true);
        if (odRet == ODR_SUB_NOT_EXIST) {
            continue;
        }
        if (odRet != ODR_OK) {
            if (errInfo != NULL) {
                *errInfo = (((uint32_t)OD_getIndex(OD_PDOMapPar)) << 8) | i;
            }
            return CO_ERROR_OD_PARAMETERS;
        }

        odRet = PDOconfigMap(PDO, map, i, isRPDO, OD);
        if (odRet != ODR_OK) {
            /* indicate erroneous mapping in initialization phase */
            OD_IO->stream.dataLength = 0;
            OD_IO->stream.dataOffset = 0xFF;
            if (*erroneousMap == 0U) {
                *erroneousMap = map;
            }
        }

        if (i < mappedObjectsCount) {
            pdoDataLength += OD_IO->stream.dataOffset;
        }
    }
#if ((CO_CONFIG_PDO) & (CO_CONFIG_PDO_BITWISE_MAPPING)) != 0
    if ((pdoDataLength > CO_PDO_MAX_SIZE * 8) || ((pdoDataLength == 0U) && (mappedObjectsCount > 0U))) {
#else
    if ((pdoDataLength > CO_PDO_MAX_SIZE) || ((pdoDataLength == 0U) && (mappedObjectsCount > 0U))) {
#endif
        if (*erroneousMap == 0U) {
            *erroneousMap = 1;
        }
    }

    if (*erroneousMap == 0U) {
        PDO->dataLength = (CO_PDO_size_t)pdoDataLength;
        PDO->mappedObjectsCount = mappedObjectsCount;
    }

    return CO_ERROR_NO;
}

#if ((CO_CONFIG_PDO)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0
/*
 * Custom function for writing OD object "PDO mapping parameter"
 *
 * For more information see file CO_ODinterface.h, OD_IO_t.
 */
static ODR_t
OD_write_PDO_mapping(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
    /* "count" is already verified in *_init() function */
    if ((stream == NULL) || (buf == NULL) || (countWritten == NULL) || (stream->subIndex > CO_PDO_MAX_MAPPED_ENTRIES)) {
        return ODR_DEV_INCOMPAT;
    }

    /* Only common part of the CO_RPDO_t or CO_TPDO_t will be used */
    CO_PDO_common_t* PDO = stream->object;

    /* PDO must be disabled before mapping configuration */
    if ((PDO->valid) || ((PDO->mappedObjectsCount != 0U) && (stream->subIndex > 0U))) {
        return ODR_UNSUPP_ACCESS;
    }

    if (stream->subIndex == 0U) {
        uint8_t mappedObjectsCount = CO_getUint8(buf);
        size_t pdoDataLength = 0;

        if (mappedObjectsCount > CO_PDO_MAX_MAPPED_ENTRIES) {
            return ODR_MAP_LEN;
        }

        /* validate enabled mapping parameters */
        for (uint8_t i = 0; i < mappedObjectsCount; i++) {
            OD_IO_t* OD_IO = &PDO->OD_IO[i];
            size_t dataLength = (size_t)OD_IO->stream.dataLength;
            size_t mappedLength = (size_t)OD_IO->stream.dataOffset;

#if ((CO_CONFIG_PDO) & (CO_CONFIG_PDO_BITWISE_MAPPING)) != 0
            if (mappedLength > dataLength * 8) {
#else
            if (mappedLength > dataLength) {
#endif
                /* erroneous map since device initial values */
                return ODR_NO_MAP;
            }
            pdoDataLength += mappedLength;
        }

#if ((CO_CONFIG_PDO) & (CO_CONFIG_PDO_BITWISE_MAPPING)) != 0
        if (pdoDataLength > CO_PDO_MAX_SIZE * 8) {
#else
        if (pdoDataLength > CO_PDO_MAX_SIZE) {
#endif
            return ODR_MAP_LEN;
        }
        if ((pdoDataLength == 0U) && (mappedObjectsCount > 0U)) {
            return ODR_INVALID_VALUE;
        }

        /* success, update PDO */
        PDO->dataLength = (CO_PDO_size_t)pdoDataLength;
        PDO->mappedObjectsCount = mappedObjectsCount;
    } else {
        uint32_t val = CO_getUint32(buf);
        ODR_t odRet = PDOconfigMap(PDO, val, stream->subIndex - 1U, PDO->isRPDO, PDO->OD);
        if (odRet != ODR_OK) {
            return odRet;
        }
    }

    /* write value to the original location in the Object Dictionary */
    return OD_writeOriginal(stream, buf, count, countWritten);
}
#endif /* (CO_CONFIG_PDO) & CO_CONFIG_FLAG_OD_DYNAMIC */
#endif /* (CO_CONFIG_PDO) & CO_CONFIG_PDO_OD_IO_ACCESS */

#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_OD_IO_ACCESS) == 0
static CO_ReturnError_t
PDO_initMapping(CO_PDO_common_t* PDO, OD_t* OD, OD_entry_t* OD_PDOMapPar, bool_t isRPDO, uint32_t* errInfo,
                uint32_t* erroneousMap) {
    ODR_t odRet;
    size_t pdoDataLength = 0;

    /* number of mapped application objects in PDO */
    uint8_t mappedObjectsCount = 0;
    odRet = OD_get_u8(OD_PDOMapPar, 0, &mappedObjectsCount, true);
    if (odRet != ODR_OK) {
        if (errInfo != NULL) {
            *errInfo = ((uint32_t)OD_getIndex(OD_PDOMapPar)) << 8;
        }
        return CO_ERROR_OD_PARAMETERS;
    }
    if (mappedObjectsCount > CO_PDO_MAX_MAPPED_ENTRIES) {
        *erroneousMap = 1;
        return CO_ERROR_NO;
    }

    /* iterate mapped OD variables */
    for (uint8_t i = 0; i < mappedObjectsCount; i++) {
        uint32_t map = 0;

        odRet = OD_get_u32(OD_PDOMapPar, i + 1, &map, true);
        if (odRet != ODR_OK) {
            if (errInfo != NULL) {
                *errInfo = (((uint32_t)OD_getIndex(OD_PDOMapPar)) << 8) | i;
            }
            return CO_ERROR_OD_PARAMETERS;
        }
        uint16_t index = (uint16_t)(map >> 16);
        uint8_t subIndex = (uint8_t)(map >> 8);
        uint8_t mappedLengthBits = (uint8_t)map;
        uint8_t mappedLength = mappedLengthBits >> 3;
        uint8_t pdoDataStart = pdoDataLength;
        pdoDataLength += mappedLength;

        if ((mappedLengthBits & 0x07) != 0 || pdoDataLength > CO_PDO_MAX_SIZE) {
            *erroneousMap = map;
            return CO_ERROR_NO;
        }

        /* is there a reference to the dummy entry */
        if (index < 0x20 && subIndex == 0) {
            for (uint8_t j = pdoDataStart; j < pdoDataLength; j++) {
                static uint8_t dummyTX = 0;
                static uint8_t dummyRX;
                PDO->mapPointer[j] = isRPDO ? &dummyRX : &dummyTX;
            }
            continue;
        }

        /* find entry in the Object Dictionary, original location */
        OD_IO_t OD_IO;
        OD_entry_t* entry = OD_find(OD, index);
        OD_attr_t testAttribute = isRPDO ? ODA_RPDO : ODA_TPDO;

        ODR_t odRet = OD_getSub(entry, subIndex, &OD_IO, true);
        if (odRet != ODR_OK || (OD_IO.stream.attribute & testAttribute) == 0 || OD_IO.stream.dataLength < mappedLength
            || OD_IO.stream.dataOrig == NULL) {
            *erroneousMap = map;
            return CO_ERROR_NO;
        }

        /* write locations to OD variable data bytes into PDO map pointers */
#ifdef CO_BIG_ENDIAN
        if ((OD_IO.stream.attribute & ODA_MB) != 0) {
            uint8_t* odDataPointer = OD_IO.stream.dataOrig + OD_IO.stream.dataLength - 1;
            for (uint8_t j = pdoDataStart; j < pdoDataLength; j++) {
                PDO->mapPointer[j] = odDataPointer--;
            }
        } else
#endif
        {
            uint8_t* odDataPointer = OD_IO.stream.dataOrig;
            for (uint8_t j = pdoDataStart; j < pdoDataLength; j++) {
                PDO->mapPointer[j] = odDataPointer++;
            }
        }

        /* get TPDO request flag byte from extension */
#if OD_FLAGS_PDO_SIZE > 0
        if (!isRPDO && subIndex < (OD_FLAGS_PDO_SIZE * 8) && entry->extension != NULL) {
            PDO->flagPDObyte[pdoDataStart] = &entry->extension->flagsPDO[subIndex >> 3];
            PDO->flagPDObitmask[pdoDataStart] = 1 << (subIndex & 0x07);
        }
#endif
    }

    PDO->dataLength = PDO->mappedObjectsCount = pdoDataLength;
    return CO_ERROR_NO;
}

#endif /* ((CO_CONFIG_PDO) & CO_CONFIG_PDO_OD_IO_ACCESS) == 0 */

#if ((CO_CONFIG_PDO)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0
/*
 * Custom function for reading OD object "PDO communication parameter"
 *
 * For more information see file CO_ODinterface.h, OD_IO_t.
 */
static ODR_t
OD_read_PDO_commParam(OD_stream_t* stream, void* buf, OD_size_t count, OD_size_t* countRead) {
    ODR_t returnCode = OD_readOriginal(stream, buf, count, countRead);

    /* When reading COB_ID, add Node-Id to the read value, if necessary */
    if ((returnCode == ODR_OK) && (stream->subIndex == 1U) && (*countRead == 4U)) {
        /* Only common part of the CO_RPDO_t or CO_TPDO_t will be used */
        CO_PDO_common_t* PDO = stream->object;
        uint32_t COB_ID = CO_getUint32(buf);
        uint16_t CAN_ID = (uint16_t)(COB_ID & 0x7FFU);

        /* If default CAN-ID is stored in OD (without Node-ID), add Node-ID */
        if ((CAN_ID != 0U) && (CAN_ID == (PDO->preDefinedCanId & 0xFF80U))) {
            COB_ID = (COB_ID & 0xFFFF0000U) | PDO->preDefinedCanId;
        }

        /* If PDO is not valid, set bit 31 */
        if (!PDO->valid) {
            COB_ID |= 0x80000000U;
        }

        (void)CO_setUint32(buf, COB_ID);
    }

    return returnCode;
}
#endif /* (CO_CONFIG_PDO) & CO_CONFIG_FLAG_OD_DYNAMIC */

/*******************************************************************************
 *      R P D O
 ******************************************************************************/
#if ((CO_CONFIG_PDO)&CO_CONFIG_RPDO_ENABLE) != 0
/*
 * @defgroup CO_PDO_receiveErrors_t States for RPDO->receiveError indicates received RPDOs with wrong length.
 * @{
 *
 */
#define CO_RPDO_RX_ACK_NO_ERROR 0U  /* No error */
#define CO_RPDO_RX_ACK_ERROR    1U  /* Error is acknowledged */
#define CO_RPDO_RX_ACK          10U /* Auxiliary value */
#define CO_RPDO_RX_OK           11U /* Correct RPDO received, not acknowledged */
#define CO_RPDO_RX_SHORT        12U /* Too short RPDO received, not acknowledged */
#define CO_RPDO_RX_LONG         13U /* Too long RPDO received, not acknowledged */

/* @} */ /* CO_PDO_receiveErrors_t */

/*
 * Read received message from CAN module.
 *
 * Function will be called (by CAN receive interrupt) every time, when CAN message with correct identifier
 * will be received. For more information and description of parameters see file CO_driver.h.
 * If new message arrives and previous message wasn't processed yet, then
 * previous message will be lost and overwritten by the new message.
 */
static void
CO_PDO_receive(void* object, void* msg) {
    CO_RPDO_t* RPDO = object;
    CO_PDO_common_t* PDO = &RPDO->PDO_common;
    uint8_t DLC = CO_CANrxMsg_readDLC(msg);
    const uint8_t* data = CO_CANrxMsg_readData(msg);
    uint8_t err = RPDO->receiveError;

    if (PDO->valid) {
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_BITWISE_MAPPING) != 0
        if (DLC >= (PDO->dataLength + 7) / 8) {
#else
        if (DLC >= PDO->dataLength) {
#endif
            /* indicate errors in PDO length */
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_BITWISE_MAPPING) != 0
            if (DLC == (PDO->dataLength + 7) / 8) {
#else
            if (DLC == PDO->dataLength) {
#endif
                if (err == CO_RPDO_RX_ACK_ERROR) {
                    err = CO_RPDO_RX_OK;
                }
            } else {
                if (err == CO_RPDO_RX_ACK_NO_ERROR) {
                    err = CO_RPDO_RX_LONG;
                }
            }

            /* Determine, to which of the two rx buffers copy the message. */
            uint8_t bufNo = 0;
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_SYNC_ENABLE) != 0
            if (RPDO->synchronous && (RPDO->SYNC != NULL) && RPDO->SYNC->CANrxToggle) {
                bufNo = 1;
            }
#endif

            /* copy data into appropriate buffer and set 'new message' flag */
            (void)memcpy(RPDO->CANrxData[bufNo], data, CO_PDO_MAX_SIZE);
            CO_FLAG_SET(RPDO->CANrxNew[bufNo]);

#if ((CO_CONFIG_PDO)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0
            /* Optional signal to RTOS, which can resume task, which handles the RPDO. */
            if (RPDO->pFunctSignalPre != NULL) {
                RPDO->pFunctSignalPre(RPDO->functSignalObjectPre);
            }
#endif
        } else if (err == CO_RPDO_RX_ACK_NO_ERROR) {
            err = CO_RPDO_RX_SHORT;
        } else { /* MISRA C 2004 14.10 */
        }
    }

    RPDO->receiveError = err;
}

#if ((CO_CONFIG_PDO)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0
/*
 * Custom function for writing OD object "RPDO communication parameter"
 *
 * For more information see file CO_ODinterface.h, OD_IO_t.
 */
static ODR_t
OD_write_14xx(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
    /* "count" is also verified in *_init() function */
    if ((stream == NULL) || (buf == NULL) || (countWritten == NULL) || (count > 4U)) {
        return ODR_DEV_INCOMPAT;
    }

    CO_RPDO_t* RPDO = stream->object;
    CO_PDO_common_t* PDO = &RPDO->PDO_common;
    uint8_t bufCopy[4];
    (void)memcpy((void*)bufCopy, (const void*)buf, count);

    switch (stream->subIndex) {
        case 1: { /* COB-ID used by PDO */
            uint32_t COB_ID = CO_getUint32(buf);
            uint16_t CAN_ID = (uint16_t)(COB_ID & 0x7FFU);
            bool_t valid = (COB_ID & 0x80000000U) == 0U;

            /* bits 11...29 must be zero, PDO must be disabled on change, CAN_ID == 0 is
             * not allowed, mapping must be configured before enabling the PDO */
            if (((COB_ID & 0x3FFFF800U) != 0U) || (valid && PDO->valid && (CAN_ID != PDO->configuredCanId))
                || (valid && CO_IS_RESTRICTED_CAN_ID(CAN_ID)) || (valid && (PDO->mappedObjectsCount == 0U))) {
                return ODR_INVALID_VALUE;
            }

            /* parameter changed? */
            if ((valid != PDO->valid) || (CAN_ID != PDO->configuredCanId)) {
                /* if default CAN-ID is written, store to OD without Node-ID */
                if (CAN_ID == PDO->preDefinedCanId) {
                    (void)CO_setUint32(bufCopy, COB_ID & 0xFFFFFF80U);
                }
                if (!valid) {
                    CAN_ID = 0;
                }

                CO_ReturnError_t ret = CO_CANrxBufferInit(PDO->CANdev, PDO->CANdevIdx, CAN_ID, 0x7FF, false,
                                                          (void*)RPDO, CO_PDO_receive);

                if (valid && (ret == CO_ERROR_NO)) {
                    PDO->valid = true;
                    PDO->configuredCanId = CAN_ID;
                } else {
                    PDO->valid = false;
                    CO_FLAG_CLEAR(RPDO->CANrxNew[0]);
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_SYNC_ENABLE) != 0
                    CO_FLAG_CLEAR(RPDO->CANrxNew[1]);
#endif
                    if (ret != CO_ERROR_NO) {
                        return ODR_DEV_INCOMPAT;
                    }
                }
            }
            break;
        }

        case 2: { /* transmission type */ uint8_t transmissionType = CO_getUint8(buf);
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_SYNC_ENABLE) != 0
            if ((transmissionType > (uint8_t)CO_PDO_TRANSM_TYPE_SYNC_240)
                && (transmissionType < (uint8_t)CO_PDO_TRANSM_TYPE_SYNC_EVENT_LO)) {
                return ODR_INVALID_VALUE;
            }

            bool_t synchronous = transmissionType <= (uint8_t)CO_PDO_TRANSM_TYPE_SYNC_240;
            /* Remove old message from the second buffer. */
            if (RPDO->synchronous != synchronous) {
                CO_FLAG_CLEAR(RPDO->CANrxNew[1]);
            }

            RPDO->synchronous = synchronous;
#else
            if (transmissionType < CO_PDO_TRANSM_TYPE_SYNC_EVENT_LO) {
                return ODR_INVALID_VALUE;
            }
#endif
            break;
        }

#if ((CO_CONFIG_PDO)&CO_CONFIG_RPDO_TIMERS_ENABLE) != 0
        case 5: { /* event-timer */
            uint32_t eventTime = CO_getUint16(buf);
            RPDO->timeoutTime_us = eventTime * 1000U;
            RPDO->timeoutTimer = 0;
            break;
        }
#endif
        default:
            /* MISRA C 2004 15.3 */
            break;
    }

    /* write value to the original location in the Object Dictionary */
    return OD_writeOriginal(stream, bufCopy, count, countWritten);
}
#endif /* (CO_CONFIG_PDO) & CO_CONFIG_FLAG_OD_DYNAMIC */

CO_ReturnError_t
CO_RPDO_init(CO_RPDO_t* RPDO, OD_t* OD, CO_EM_t* em,
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_SYNC_ENABLE) != 0
             CO_SYNC_t* SYNC,
#endif
             uint16_t preDefinedCanId, OD_entry_t* OD_14xx_RPDOCommPar, OD_entry_t* OD_16xx_RPDOMapPar,
             CO_CANmodule_t* CANdevRx, uint16_t CANdevRxIdx, uint32_t* errInfo) {
    CO_PDO_common_t* PDO = &RPDO->PDO_common;
    CO_ReturnError_t ret;
    ODR_t odRet;

    /* verify arguments */
    if ((RPDO == NULL) || (OD == NULL) || (em == NULL) || (OD_14xx_RPDOCommPar == NULL) || (OD_16xx_RPDOMapPar == NULL)
        || (CANdevRx == NULL)) {
        return CO_ERROR_ILLEGAL_ARGUMENT;
    }

    /* clear object */
    (void)memset(RPDO, 0, sizeof(CO_RPDO_t));

    /* Configure object variables */
    PDO->em = em;
    PDO->CANdev = CANdevRx;

    /* Configure mapping parameters */
    uint32_t erroneousMap = 0;
    ret = PDO_initMapping(PDO, OD, OD_16xx_RPDOMapPar, true, errInfo, &erroneousMap);
    if (ret != CO_ERROR_NO) {
        return ret;
    }

    /* Configure communication parameter - COB-ID */
    uint32_t COB_ID = 0;
    odRet = OD_get_u32(OD_14xx_RPDOCommPar, 1, &COB_ID, true);
    if (odRet != ODR_OK) {
        if (errInfo != NULL) {
            *errInfo = (((uint32_t)OD_getIndex(OD_14xx_RPDOCommPar)) << 8) | 1U;
        }
        return CO_ERROR_OD_PARAMETERS;
    }

    bool_t valid = (COB_ID & 0x80000000U) == 0U;
    uint16_t CAN_ID = (uint16_t)(COB_ID & 0x7FFU);
    if (valid && ((PDO->mappedObjectsCount == 0U) || (CAN_ID == 0U))) {
        valid = false;
        if (erroneousMap == 0U) {
            erroneousMap = 1;
        }
    }

    if (erroneousMap != 0U) {
        CO_errorReport(PDO->em, CO_EM_PDO_WRONG_MAPPING, CO_EMC_PROTOCOL_ERROR,
                       (erroneousMap != 1U) ? erroneousMap : COB_ID);
    }
    if (!valid) {
        CAN_ID = 0;
    }

    /* If default CAN-ID is stored in OD (without Node-ID), add Node-ID */
    if ((CAN_ID != 0U) && (CAN_ID == (preDefinedCanId & 0xFF80U))) {
        CAN_ID = preDefinedCanId;
    }

    ret = CO_CANrxBufferInit(CANdevRx, CANdevRxIdx, CAN_ID, 0x7FF, false, (void*)RPDO, CO_PDO_receive);
    if (ret != CO_ERROR_NO) {
        return ret;
    }

    PDO->valid = valid;

    /* Configure communication parameter - transmission type */
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_SYNC_ENABLE) != 0
    uint8_t transmissionType = (uint8_t)(CO_PDO_TRANSM_TYPE_SYNC_EVENT_LO);
    odRet = OD_get_u8(OD_14xx_RPDOCommPar, 2, &transmissionType, true);
    if (odRet != ODR_OK) {
        if (errInfo != NULL) {
            *errInfo = (((uint32_t)OD_getIndex(OD_14xx_RPDOCommPar)) << 8) | 2U;
        }
        return CO_ERROR_OD_PARAMETERS;
    }

    RPDO->SYNC = SYNC;
    RPDO->synchronous = transmissionType <= (uint8_t)CO_PDO_TRANSM_TYPE_SYNC_240;
#endif

    /* Configure communication parameter - event-timer (optional) */
#if ((CO_CONFIG_PDO)&CO_CONFIG_RPDO_TIMERS_ENABLE) != 0
    uint16_t eventTime = 0;
    (void)OD_get_u16(OD_14xx_RPDOCommPar, 5, &eventTime, true);
    RPDO->timeoutTime_us = (uint32_t)eventTime * 1000U;
#endif

    /* Configure OD extensions */
#if ((CO_CONFIG_PDO)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0
    PDO->isRPDO = true;
    PDO->OD = OD;
    PDO->CANdevIdx = CANdevRxIdx;
    PDO->preDefinedCanId = preDefinedCanId;
    PDO->configuredCanId = CAN_ID;
    PDO->OD_communicationParam_ext.object = RPDO;
    PDO->OD_communicationParam_ext.read = OD_read_PDO_commParam;
    PDO->OD_communicationParam_ext.write = OD_write_14xx;
    PDO->OD_mappingParam_extension.object = RPDO;
    PDO->OD_mappingParam_extension.read = OD_readOriginal;
    PDO->OD_mappingParam_extension.write = OD_write_PDO_mapping;
    (void)OD_extension_init(OD_14xx_RPDOCommPar, &PDO->OD_communicationParam_ext);
    (void)OD_extension_init(OD_16xx_RPDOMapPar, &PDO->OD_mappingParam_extension);
#endif

    return CO_ERROR_NO;
}

#if ((CO_CONFIG_PDO)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0
void
CO_RPDO_initCallbackPre(CO_RPDO_t* RPDO, void* object, void (*pFunctSignalPre)(void* object)) {
    if (RPDO != NULL) {
        RPDO->functSignalObjectPre = object;
        RPDO->pFunctSignalPre = pFunctSignalPre;
    }
}
#endif

void
CO_RPDO_process(CO_RPDO_t* RPDO,
#if ((CO_CONFIG_PDO)&CO_CONFIG_RPDO_TIMERS_ENABLE) != 0
                uint32_t timeDifference_us, uint32_t* timerNext_us,
#endif
                bool_t NMTisOperational, bool_t syncWas) {
    (void)syncWas;
#if ((CO_CONFIG_PDO)&CO_CONFIG_RPDO_TIMERS_ENABLE) != 0
    (void)timerNext_us;
#endif

    CO_PDO_common_t* PDO = &RPDO->PDO_common;

    if (PDO->valid && NMTisOperational
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_SYNC_ENABLE) != 0
        && (syncWas || !RPDO->synchronous)
#endif
    ) {
        /* Verify errors in length of received RPDO CAN message */
        if (RPDO->receiveError > CO_RPDO_RX_ACK) {
            bool_t setError = RPDO->receiveError != CO_RPDO_RX_OK;
            uint16_t code = (RPDO->receiveError == CO_RPDO_RX_SHORT) ? CO_EMC_PDO_LENGTH : CO_EMC_PDO_LENGTH_EXC;
            CO_error(PDO->em, setError, CO_EM_RPDO_WRONG_LENGTH, code, PDO->dataLength);
            RPDO->receiveError = setError ? CO_RPDO_RX_ACK_ERROR : CO_RPDO_RX_ACK_NO_ERROR;
        }

        /* Determine, which of the two rx buffers contains relevant message. */
        uint8_t bufNo = 0;
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_SYNC_ENABLE) != 0
        if (RPDO->synchronous && (RPDO->SYNC != NULL) && !RPDO->SYNC->CANrxToggle) {
            bufNo = 1;
        }
#endif

        /* copy RPDO into OD variables according to mappings */
        bool_t rpdoReceived = false;
        while (CO_FLAG_READ(RPDO->CANrxNew[bufNo])) {
            rpdoReceived = true;
            uint8_t* dataRPDO = RPDO->CANrxData[bufNo];
            OD_size_t verifyLength = 0U;

            /* Clear the flag. If between the copy operation CANrxNew is set
             * by receive thread, then copy the latest data again. */
            CO_FLAG_CLEAR(RPDO->CANrxNew[bufNo]);

#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_BITWISE_MAPPING) != 0
            /* Copy everything regardless of the actual PDO size to UINT64 to simplify bit shifting */
            uint64_t buf64 = 0;
            (void)memcpy(&buf64, dataRPDO, CO_PDO_MAX_SIZE);
#ifdef CO_BIG_ENDIAN
            /* For big endian we need to swap all bytes */
            buf64 = CO_SWAP_64(buf64);
#endif /* CO_BIG_ENDIAN */
#endif /* ((CO_CONFIG_PDO)&CO_CONFIG_PDO_BITWISE_MAPPING) */
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_OD_IO_ACCESS) != 0
            for (uint8_t i = 0; i < PDO->mappedObjectsCount; i++) {
                OD_IO_t* OD_IO = &PDO->OD_IO[i];

                /* get mappedLength from temporary storage */
                OD_size_t* dataOffset = &OD_IO->stream.dataOffset;
                uint8_t mappedLength = (uint8_t)(*dataOffset);

                /* additional safety check. */
                verifyLength += (OD_size_t)mappedLength;
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_BITWISE_MAPPING) != 0
                if (verifyLength > CO_PDO_MAX_SIZE * 8) {
#else
                if (verifyLength > CO_PDO_MAX_SIZE) {
#endif
                    break;
                }

                /* length of OD variable may be larger than mappedLength */
                OD_size_t ODdataLength = OD_IO->stream.dataLength;
                if (ODdataLength > CO_PDO_MAX_SIZE) {
                    ODdataLength = CO_PDO_MAX_SIZE;
                }
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_BITWISE_MAPPING) != 0
                /* Prepare data for writing into OD variable. If mappedLength
                 * is smaller than ODdataLength, then use auxiliary buffer */
                uint8_t* dataOD;
                /* Apply the bitmask */
                uint64_t shiftedData = buf64 & (UINT64_MAX >> (64 - mappedLength));
                /* Shift the original buffer to get ready for the next mapping */
                buf64 >>= mappedLength;
#ifdef CO_BIG_ENDIAN
                /* Adjust the pointer ignoring unused most significant bytes */
                dataOD = (uint8_t *)&shiftedData + 8 - ODdataLength;
#else
                /* For little-endian we have a pointer to LSB */
                dataOD = (uint8_t *)&shiftedData;
#endif
#else
                /* Prepare data for writing into OD variable. If mappedLength
                 * is smaller than ODdataLength, then use auxiliary buffer */
                uint8_t buf[CO_PDO_MAX_SIZE];
                uint8_t* dataOD;
                if (ODdataLength > mappedLength) {
                    (void)memset(buf, 0, sizeof(buf));
                    (void)memcpy(buf, dataRPDO, mappedLength);
                    dataOD = buf;
                } else {
                    dataOD = dataRPDO;
                }

                /* swap multibyte data if big-endian */
#ifdef CO_BIG_ENDIAN
                if ((OD_IO->stream.attribute & ODA_MB) != 0) {
                    uint8_t* lo = dataOD;
                    uint8_t* hi = dataOD + ODdataLength - 1;
                    while (lo < hi) {
                        uint8_t swap = *lo;
                        *lo++ = *hi;
                        *hi-- = swap;
                    }
                }
#endif
#endif /* (CO_CONFIG_PDO)&CO_CONFIG_PDO_BITWISE_MAPPING */

                /* Set stream.dataOffset to zero, perform OD_IO.write()
                 * and store mappedLength back to stream.dataOffset */
                *dataOffset = 0;
                OD_size_t countWritten;
                OD_IO->write(&OD_IO->stream, dataOD, ODdataLength, &countWritten);
                *dataOffset = mappedLength;

                dataRPDO += mappedLength;
            }

#else
            verifyLength = (OD_size_t)PDO->dataLength;
            for (uint8_t i = 0; i < PDO->dataLength; i++) {
                *PDO->mapPointer[i] = dataRPDO[i];
            }
#endif /* (CO_CONFIG_PDO) & CO_CONFIG_PDO_OD_IO_ACCESS */

#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_BITWISE_MAPPING) != 0
            if ((verifyLength > (CO_PDO_MAX_SIZE * 8)) || (verifyLength != (OD_size_t)PDO->dataLength)) {
#else
            if ((verifyLength > CO_PDO_MAX_SIZE) || (verifyLength != (OD_size_t)PDO->dataLength)) {
#endif
                /* bug in software, should not happen */
                CO_errorReport(PDO->em, CO_EM_GENERIC_SOFTWARE_ERROR, CO_EMC_SOFTWARE_INTERNAL,
                               (0x100000U | verifyLength));
            }
        } /* while (CO_FLAG_READ(RPDO->CANrxNew[bufNo])) */

        /* verify RPDO timeout */
        (void)rpdoReceived;
#if ((CO_CONFIG_PDO)&CO_CONFIG_RPDO_TIMERS_ENABLE) != 0
        if (RPDO->timeoutTime_us > 0U) {
            if (rpdoReceived) {
                if (RPDO->timeoutTimer > RPDO->timeoutTime_us) {
                    CO_errorReset(PDO->em, CO_EM_RPDO_TIME_OUT, RPDO->timeoutTimer);
                }
                /* enable monitoring */
                RPDO->timeoutTimer = 1;
            } else if ((RPDO->timeoutTimer > 0U) && (RPDO->timeoutTimer < RPDO->timeoutTime_us)) {
                RPDO->timeoutTimer += timeDifference_us;

                if (RPDO->timeoutTimer > RPDO->timeoutTime_us) {
                    CO_errorReport(PDO->em, CO_EM_RPDO_TIME_OUT, CO_EMC_RPDO_TIMEOUT, RPDO->timeoutTimer);
                }
            } else { /* MISRA C 2004 14.10 */
            }
#if ((CO_CONFIG_PDO)&CO_CONFIG_FLAG_TIMERNEXT) != 0
            if ((timerNext_us != NULL) && (RPDO->timeoutTimer < RPDO->timeoutTime_us)) {
                uint32_t diff = RPDO->timeoutTime_us - RPDO->timeoutTimer;
                if (*timerNext_us > diff) {
                    *timerNext_us = diff;
                }
            }
#endif
        }
#endif /* (CO_CONFIG_PDO) & CO_CONFIG_RPDO_TIMERS_ENABLE */
    }  /* if (PDO->valid && NMTisOperational) */
    else {
        /* not valid and operational, clear CAN receive flags and timeoutTimer */
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_SYNC_ENABLE) != 0
        if (!PDO->valid || !NMTisOperational) {
            CO_FLAG_CLEAR(RPDO->CANrxNew[0]);
            CO_FLAG_CLEAR(RPDO->CANrxNew[1]);
#if ((CO_CONFIG_PDO)&CO_CONFIG_RPDO_TIMERS_ENABLE) != 0
            RPDO->timeoutTimer = 0;
#endif
        }
#else
        CO_FLAG_CLEAR(RPDO->CANrxNew[0]);
#if ((CO_CONFIG_PDO)&CO_CONFIG_RPDO_TIMERS_ENABLE) != 0
        RPDO->timeoutTimer = 0;
#endif
#endif
    }
}
#endif /* (CO_CONFIG_PDO) & CO_CONFIG_RPDO_ENABLE */

/*******************************************************************************
 *      T P D O
 ******************************************************************************/
#if ((CO_CONFIG_PDO)&CO_CONFIG_TPDO_ENABLE) != 0
#if ((CO_CONFIG_PDO)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0
/*
 * Custom function for writing OD object "TPDO communication parameter"
 *
 * For more information see file CO_ODinterface.h, OD_IO_t.
 */
static ODR_t
OD_write_18xx(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
    /* "count" is also verified in *_init() function */
    if ((stream == NULL) || (buf == NULL) || (countWritten == NULL) || (count > 4U)) {
        return ODR_DEV_INCOMPAT;
    }

    CO_TPDO_t* TPDO = stream->object;
    CO_PDO_common_t* PDO = &TPDO->PDO_common;
    uint8_t bufCopy[4];
    (void)memcpy((void*)bufCopy, (const void*)buf, count);

    switch (stream->subIndex) {
        case 1: { /* COB-ID used by PDO */
            uint32_t COB_ID = CO_getUint32(buf);
            uint16_t CAN_ID = (uint16_t)(COB_ID & 0x7FFU);
            bool_t valid = (COB_ID & 0x80000000U) == 0U;

            /* bits 11...29 must be zero, PDO must be disabled on change, CAN_ID == 0 is
             * not allowed, mapping must be configured before enabling the PDO */
            if (((COB_ID & 0x3FFFF800U) != 0U) || (valid && (PDO->valid && (CAN_ID != PDO->configuredCanId)))
                || (valid && CO_IS_RESTRICTED_CAN_ID(CAN_ID)) || (valid && (PDO->mappedObjectsCount == 0U))) {
                return ODR_INVALID_VALUE;
            }

            /* parameter changed? */
            if ((valid != PDO->valid) || (CAN_ID != PDO->configuredCanId)) {
                /* if default CAN-ID is written, store to OD without Node-ID */
                if (CAN_ID == PDO->preDefinedCanId) {
                    (void)CO_setUint32(bufCopy, COB_ID & 0xFFFFFF80U);
                }
                if (!valid) {
                    CAN_ID = 0;
                }

                CO_CANtx_t* CANtxBuff = CO_CANtxBufferInit(
                    PDO->CANdev, PDO->CANdevIdx, CAN_ID, false, PDO->dataLength,
                    TPDO->transmissionType <= (uint8_t)CO_PDO_TRANSM_TYPE_SYNC_240);

                if (CANtxBuff == NULL) {
                    return ODR_DEV_INCOMPAT;
                }

                TPDO->CANtxBuff = CANtxBuff;
                PDO->valid = valid;
                PDO->configuredCanId = CAN_ID;
            }
            break;
        }

        case 2: { /* transmission type */ uint8_t transmissionType = CO_getUint8(buf);
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_SYNC_ENABLE) != 0
            if ((transmissionType > (uint8_t)CO_PDO_TRANSM_TYPE_SYNC_240)
                && (transmissionType < (uint8_t)CO_PDO_TRANSM_TYPE_SYNC_EVENT_LO)) {
                return ODR_INVALID_VALUE;
            }
            TPDO->CANtxBuff->syncFlag = transmissionType <= (uint8_t)CO_PDO_TRANSM_TYPE_SYNC_240;
            TPDO->syncCounter = 255;
#else
            if (transmissionType < CO_PDO_TRANSM_TYPE_SYNC_EVENT_LO) {
                return ODR_INVALID_VALUE;
            }
#endif
            TPDO->transmissionType = transmissionType;
            TPDO->sendRequest = true;
#if ((CO_CONFIG_PDO)&CO_CONFIG_TPDO_TIMERS_ENABLE) != 0
            TPDO->inhibitTimer = 0;
            TPDO->eventTimer = 0;
#endif
            break;
        }

#if ((CO_CONFIG_PDO)&CO_CONFIG_TPDO_TIMERS_ENABLE) != 0
        case 3: { /* inhibit time */
            if (PDO->valid) {
                return ODR_INVALID_VALUE;
            }
            uint32_t inhibitTime = CO_getUint16(buf);
            TPDO->inhibitTime_us = inhibitTime * 100U;
            TPDO->inhibitTimer = 0;
            break;
        }

        case 5: { /* event-timer */
            uint32_t eventTime = CO_getUint16(buf);
            TPDO->eventTime_us = eventTime * 1000U;
            TPDO->eventTimer = 0;
            break;
        }
#endif

#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_SYNC_ENABLE) != 0
        case 6: { /* SYNC start value */
            uint8_t syncStartValue = CO_getUint8(buf);

            if (PDO->valid || (syncStartValue > 240U)) {
                return ODR_INVALID_VALUE;
            }
            TPDO->syncStartValue = syncStartValue;
            break;
        }
#endif
        default:
            /* MISRA C 2004 15.3 */
            break;
    }

    /* write value to the original location in the Object Dictionary */
    return OD_writeOriginal(stream, bufCopy, count, countWritten);
}
#endif /* (CO_CONFIG_PDO) & CO_CONFIG_FLAG_OD_DYNAMIC */

CO_ReturnError_t
CO_TPDO_init(CO_TPDO_t* TPDO, OD_t* OD, CO_EM_t* em,
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_SYNC_ENABLE) != 0
             CO_SYNC_t* SYNC,
#endif
             uint16_t preDefinedCanId, OD_entry_t* OD_18xx_TPDOCommPar, OD_entry_t* OD_1Axx_TPDOMapPar,
             CO_CANmodule_t* CANdevTx, uint16_t CANdevTxIdx, uint32_t* errInfo) {
    CO_PDO_common_t* PDO = &TPDO->PDO_common;
    ODR_t odRet;

    /* verify arguments */
    if ((TPDO == NULL) || (OD == NULL) || (em == NULL) || (OD_18xx_TPDOCommPar == NULL) || (OD_1Axx_TPDOMapPar == NULL)
        || (CANdevTx == NULL)) {
        return CO_ERROR_ILLEGAL_ARGUMENT;
    }

    /* clear object */
    (void)memset(TPDO, 0, sizeof(CO_TPDO_t));

    /* Configure object variables */
    PDO->em = em;
    PDO->CANdev = CANdevTx;

    /* Configure mapping parameters */
    uint32_t erroneousMap = 0;
    CO_ReturnError_t ret = PDO_initMapping(PDO, OD, OD_1Axx_TPDOMapPar, false, errInfo, &erroneousMap);
    if (ret != CO_ERROR_NO) {
        return ret;
    }

    /* Configure communication parameter - transmission type */
    uint8_t transmissionType = (uint8_t)(CO_PDO_TRANSM_TYPE_SYNC_EVENT_LO);
    odRet = OD_get_u8(OD_18xx_TPDOCommPar, 2, &transmissionType, true);
    if (odRet != ODR_OK) {
        if (errInfo != NULL) {
            *errInfo = (((uint32_t)OD_getIndex(OD_18xx_TPDOCommPar)) << 8) | 2U;
        }
        return CO_ERROR_OD_PARAMETERS;
    }
    if ((transmissionType < (uint8_t)CO_PDO_TRANSM_TYPE_SYNC_EVENT_LO)
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_SYNC_ENABLE) != 0
        && (transmissionType > (uint8_t)CO_PDO_TRANSM_TYPE_SYNC_240)
#endif
    ) {
        transmissionType = (uint8_t)(CO_PDO_TRANSM_TYPE_SYNC_EVENT_LO);
    }
    TPDO->transmissionType = transmissionType;
    TPDO->sendRequest = true;

    /* Configure communication parameter - COB-ID */
    uint32_t COB_ID = 0;
    odRet = OD_get_u32(OD_18xx_TPDOCommPar, 1, &COB_ID, true);
    if (odRet != ODR_OK) {
        if (errInfo != NULL) {
            *errInfo = (((uint32_t)OD_getIndex(OD_18xx_TPDOCommPar)) << 8) | 1U;
        }
        return CO_ERROR_OD_PARAMETERS;
    }

    bool_t valid = (COB_ID & 0x80000000U) == 0U;
    uint16_t CAN_ID = (uint16_t)(COB_ID & 0x7FFU);
    if (valid && ((PDO->mappedObjectsCount == 0U) || (CAN_ID == 0U))) {
        valid = false;
        if (erroneousMap == 0U) {
            erroneousMap = 1;
        }
    }

    if (erroneousMap != 0U) {
        CO_errorReport(PDO->em, CO_EM_PDO_WRONG_MAPPING, CO_EMC_PROTOCOL_ERROR,
                       (erroneousMap != 1U) ? erroneousMap : COB_ID);
    }
    if (!valid) {
        CAN_ID = 0;
    }

    /* If default CAN-ID is stored in OD (without Node-ID), add Node-ID */
    if ((CAN_ID != 0U) && (CAN_ID == (preDefinedCanId & 0xFF80U))) {
        CAN_ID = preDefinedCanId;
    }

#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_BITWISE_MAPPING) != 0
    TPDO->CANtxBuff = CO_CANtxBufferInit(CANdevTx, CANdevTxIdx, CAN_ID, false, (PDO->dataLength + 7) / 8,
                                         TPDO->transmissionType <= (uint8_t)CO_PDO_TRANSM_TYPE_SYNC_240);
#else
    TPDO->CANtxBuff = CO_CANtxBufferInit(CANdevTx, CANdevTxIdx, CAN_ID, false, PDO->dataLength,
                                         TPDO->transmissionType <= (uint8_t)CO_PDO_TRANSM_TYPE_SYNC_240);
#endif

    if (TPDO->CANtxBuff == NULL) {
        return CO_ERROR_ILLEGAL_ARGUMENT;
    }

    PDO->valid = valid;

    /* Configure communication parameter - inhibit time and event-timer (opt) */
#if ((CO_CONFIG_PDO)&CO_CONFIG_TPDO_TIMERS_ENABLE) != 0
    uint16_t inhibitTime = 0;
    uint16_t eventTime = 0;
    (void)OD_get_u16(OD_18xx_TPDOCommPar, 3, &inhibitTime, true);
    (void)OD_get_u16(OD_18xx_TPDOCommPar, 5, &eventTime, true);
    TPDO->inhibitTime_us = (uint32_t)inhibitTime * 100U;
    TPDO->eventTime_us = (uint32_t)eventTime * 1000U;
#endif

    /* Configure communication parameter - SYNC start value (optional) */
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_SYNC_ENABLE) != 0
    TPDO->syncStartValue = 0;
    (void)OD_get_u8(OD_18xx_TPDOCommPar, 6, &TPDO->syncStartValue, true);
    TPDO->SYNC = SYNC;
    TPDO->syncCounter = 255;
#endif

    /* Configure OD extensions */
#if ((CO_CONFIG_PDO)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0
    PDO->isRPDO = false;
    PDO->OD = OD;
    PDO->CANdevIdx = CANdevTxIdx;
    PDO->preDefinedCanId = preDefinedCanId;
    PDO->configuredCanId = CAN_ID;
    PDO->OD_communicationParam_ext.object = TPDO;
    PDO->OD_communicationParam_ext.read = OD_read_PDO_commParam;
    PDO->OD_communicationParam_ext.write = OD_write_18xx;
    PDO->OD_mappingParam_extension.object = TPDO;
    PDO->OD_mappingParam_extension.read = OD_readOriginal;
    PDO->OD_mappingParam_extension.write = OD_write_PDO_mapping;
    (void)OD_extension_init(OD_18xx_TPDOCommPar, &PDO->OD_communicationParam_ext);
    (void)OD_extension_init(OD_1Axx_TPDOMapPar, &PDO->OD_mappingParam_extension);
#endif

    return CO_ERROR_NO;
}

/*
 * Send TPDO message.
 *
 * Function prepares TPDO data from Object Dictionary variables. It is called
 * from CO_TPDO_process() according to TPDO communication parameters.
 *
 * @param TPDO TPDO object.
 *
 * @return Same as CO_CANsend().
 */
static CO_ReturnError_t
CO_TPDOsend(CO_TPDO_t* TPDO) {
    CO_PDO_common_t* PDO = &TPDO->PDO_common;
    uint8_t* dataTPDO = &TPDO->CANtxBuff->data[0];
    OD_size_t verifyLength = 0U;

#if OD_FLAGS_PDO_SIZE > 0
    bool_t eventDriven = ((TPDO->transmissionType == (uint8_t)CO_PDO_TRANSM_TYPE_SYNC_ACYCLIC)
                          || (TPDO->transmissionType >= (uint8_t)CO_PDO_TRANSM_TYPE_SYNC_EVENT_LO));
#endif

#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_OD_IO_ACCESS) != 0
#if ((CO_CONFIG_PDO) & (CO_CONFIG_PDO_BITWISE_MAPPING)) != 0
        uint64_t buf64 = 0;
#endif
    for (uint8_t i = 0; i < PDO->mappedObjectsCount; i++) {
        OD_IO_t* OD_IO = &PDO->OD_IO[i];
        OD_stream_t* stream = &OD_IO->stream;

        /* get mappedLength from temporary storage */
        uint8_t mappedLength = (uint8_t)stream->dataOffset;

        /* additional safety check */
        verifyLength += (OD_size_t)mappedLength;
#if ((CO_CONFIG_PDO) & (CO_CONFIG_PDO_BITWISE_MAPPING)) != 0
        if (verifyLength > CO_PDO_MAX_SIZE * 8) {
#else
        if (verifyLength > CO_PDO_MAX_SIZE) {
#endif
            break;
        }

        /* length of OD variable may be larger than mappedLength */
        OD_size_t ODdataLength = stream->dataLength;
        if (ODdataLength > CO_PDO_MAX_SIZE) {
            ODdataLength = CO_PDO_MAX_SIZE;
        }
        /* If mappedLength is smaller than ODdataLength, use auxiliary buffer */
#if ((CO_CONFIG_PDO) & (CO_CONFIG_PDO_BITWISE_MAPPING)) != 0
        uint64_t buf = 0;
#else
        uint8_t buf[CO_PDO_MAX_SIZE];
        uint8_t* dataTPDOCopy;
        if (ODdataLength > mappedLength) {
            (void)memset(buf, 0, sizeof(buf));
            dataTPDOCopy = buf;
        } else {
            dataTPDOCopy = dataTPDO;
        }
#endif /* (CO_CONFIG_PDO) & (CO_CONFIG_PDO_BITWISE_MAPPING) */

        /* Set stream.dataOffset to zero, perform OD_IO.read() and store mappedLength back to stream.dataOffset */
        stream->dataOffset = 0;
        OD_size_t countRd;
#if ((CO_CONFIG_PDO) & (CO_CONFIG_PDO_BITWISE_MAPPING)) != 0
        OD_IO->read(stream, &buf, ODdataLength, &countRd);
#ifdef CO_BIG_ENDIAN
        /* Shift right according to the OD data length in bytes to compensate the difference in byte length
         * For LE not needed, low bytes from the OD entry are copied to low bytes of u64 */
        buf >>= 64 - 8 * ODdataLength;
#endif /* CO_BIG_ENDIAN */
        /* Apply the mask and merge with the rest */
        buf &= (UINT64_MAX >> (64 - mappedLength));
        buf <<= (verifyLength  - mappedLength);
        buf64 |= buf;
#else
        OD_IO->read(stream, dataTPDOCopy, ODdataLength, &countRd);
#endif /* (CO_CONFIG_PDO) & (CO_CONFIG_PDO_BITWISE_MAPPING) */
        stream->dataOffset = mappedLength;

#if ((CO_CONFIG_PDO) & (CO_CONFIG_PDO_BITWISE_MAPPING)) == 0
        /* swap multibyte data if big-endian */
#ifdef CO_BIG_ENDIAN
        if ((stream->attribute & ODA_MB) != 0) {
            uint8_t* lo = dataTPDOCopy;
            uint8_t* hi = dataTPDOCopy + ODdataLength - 1;
            while (lo < hi) {
                uint8_t swap = *lo;
                *lo++ = *hi;
                *hi-- = swap;
            }
        }
#endif

        /* If auxiliary buffer, copy it to the TPDO */
        if (ODdataLength > mappedLength) {
            (void)memcpy(dataTPDO, buf, mappedLength);
        }
#endif /* (CO_CONFIG_PDO) & (CO_CONFIG_PDO_BITWISE_MAPPING) == 0*/

        /* In event driven TPDO indicate transmission of OD variable */
#if OD_FLAGS_PDO_SIZE > 0
        uint8_t* flagPDObyte = PDO->flagPDObyte[i];
        if ((flagPDObyte != NULL) && eventDriven) {
            *flagPDObyte |= PDO->flagPDObitmask[i];
        }
#endif

#if ((CO_CONFIG_PDO) & (CO_CONFIG_PDO_BITWISE_MAPPING)) == 0
        dataTPDO += mappedLength;
#endif
    }
#else
    verifyLength = (OD_size_t)PDO->dataLength;
    for (uint8_t i = 0; i < PDO->dataLength; i++) {
        dataTPDO[i] = *PDO->mapPointer[i];

        /* In event driven TPDO indicate transmission of OD variable */
#if OD_FLAGS_PDO_SIZE > 0
        uint8_t* flagPDObyte = PDO->flagPDObyte[i];
        if (flagPDObyte != NULL && eventDriven) {
            *flagPDObyte |= PDO->flagPDObitmask[i];
        }
#endif
    }
#endif /* (CO_CONFIG_PDO) & CO_CONFIG_PDO_OD_IO_ACCESS */

#if ((CO_CONFIG_PDO) & (CO_CONFIG_PDO_BITWISE_MAPPING)) != 0
    if ((verifyLength > (CO_PDO_MAX_SIZE * 8)) || (verifyLength != (OD_size_t)PDO->dataLength)) {
#else
    if ((verifyLength > CO_PDO_MAX_SIZE) || (verifyLength != (OD_size_t)PDO->dataLength)) {
#endif
        /* bug in software, should not happen */
        CO_errorReport(PDO->em, CO_EM_GENERIC_SOFTWARE_ERROR, CO_EMC_SOFTWARE_INTERNAL, (0x200000U | verifyLength));
        return CO_ERROR_DATA_CORRUPT;
    }

#if ((CO_CONFIG_PDO) & (CO_CONFIG_PDO_BITWISE_MAPPING)) != 0
#ifdef CO_BIG_ENDIAN
    buf64 = CO_SWAP_64(buf64);
#endif
    /* Copy the calculated uint64 data to the PDO buffer */
    (void)memcpy(dataTPDO, &buf64, CO_PDO_MAX_SIZE);
#endif /* (CO_CONFIG_PDO) & (CO_CONFIG_PDO_BITWISE_MAPPING) */

    TPDO->sendRequest = false;
#if ((CO_CONFIG_PDO)&CO_CONFIG_TPDO_TIMERS_ENABLE) != 0
    TPDO->eventTimer = TPDO->eventTime_us;
    TPDO->inhibitTimer = TPDO->inhibitTime_us;
#endif
    return CO_CANsend(PDO->CANdev, TPDO->CANtxBuff);
}

void
CO_TPDO_process(CO_TPDO_t* TPDO,
#if (((CO_CONFIG_PDO)&CO_CONFIG_TPDO_TIMERS_ENABLE) != 0) || defined CO_DOXYGEN
                uint32_t timeDifference_us, uint32_t* timerNext_us,
#endif
                bool_t NMTisOperational, bool_t syncWas) {
    CO_PDO_common_t* PDO = &TPDO->PDO_common;
#if (((CO_CONFIG_PDO)&CO_CONFIG_TPDO_TIMERS_ENABLE)) != 0
    (void)timerNext_us;
#endif
    (void)syncWas;

    if (PDO->valid && NMTisOperational) {

        /* check for event timer or application event */
#if (((CO_CONFIG_PDO)&CO_CONFIG_TPDO_TIMERS_ENABLE) != 0) || (OD_FLAGS_PDO_SIZE > 0)
        if ((TPDO->transmissionType == (uint8_t)CO_PDO_TRANSM_TYPE_SYNC_ACYCLIC)
            || (TPDO->transmissionType >= (uint8_t)CO_PDO_TRANSM_TYPE_SYNC_EVENT_LO)) {
            /* event timer */
#if ((CO_CONFIG_PDO)&CO_CONFIG_TPDO_TIMERS_ENABLE) != 0
            if (TPDO->eventTime_us != 0U) {
                TPDO->eventTimer = (TPDO->eventTimer > timeDifference_us) ? (TPDO->eventTimer - timeDifference_us) : 0U;
                if (TPDO->eventTimer == 0U) {
                    TPDO->sendRequest = true;
                }
#if ((CO_CONFIG_PDO)&CO_CONFIG_FLAG_TIMERNEXT) != 0
                if ((timerNext_us != NULL) && (*timerNext_us > TPDO->eventTimer)) {
                    /* Schedule for next event time */
                    *timerNext_us = TPDO->eventTimer;
                }
#endif
            }
#endif
            /* check for any OD_requestTPDO() */
#if OD_FLAGS_PDO_SIZE > 0
            if (!TPDO->sendRequest) {
                for (uint8_t i = 0; i < PDO->mappedObjectsCount; i++) {
                    uint8_t* flagPDObyte = PDO->flagPDObyte[i];
                    if (flagPDObyte != NULL) {
                        if ((*flagPDObyte & PDO->flagPDObitmask[i]) == 0U) {
                            TPDO->sendRequest = true;
                            break;
                        }
                    }
                }
            }
#endif
        }
#endif /* ((CO_CONFIG_PDO)&CO_CONFIG_TPDO_TIMERS_ENABLE)||(OD_FLAGS_PDO_SIZE>0) */

        /* Send PDO by application request or by Event timer */
        if (TPDO->transmissionType >= (uint8_t)CO_PDO_TRANSM_TYPE_SYNC_EVENT_LO) {
#if ((CO_CONFIG_PDO)&CO_CONFIG_TPDO_TIMERS_ENABLE) != 0
            TPDO->inhibitTimer = (TPDO->inhibitTimer > timeDifference_us) ? (TPDO->inhibitTimer - timeDifference_us)
                                                                          : 0U;

            /* send TPDO */
            if (TPDO->sendRequest && (TPDO->inhibitTimer == 0U)) {
                (void)CO_TPDOsend(TPDO);
            }

#if ((CO_CONFIG_PDO)&CO_CONFIG_FLAG_TIMERNEXT) != 0
            if (TPDO->sendRequest && (timerNext_us != NULL) && (*timerNext_us > TPDO->inhibitTimer)) {
                /* Schedule for just beyond inhibit window */
                *timerNext_us = TPDO->inhibitTimer;
            }
#endif
#else
            if (TPDO->sendRequest) {
                (void)CO_TPDOsend(TPDO);
            }
#endif
        } /* if (TPDO->transmissionType >= CO_PDO_TRANSM_TYPE_SYNC_EVENT_LO) */

        /* Synchronous PDOs */
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_SYNC_ENABLE) != 0
        else if ((TPDO->SYNC != NULL) && syncWas) {
            /* send synchronous acyclic TPDO */
            if (TPDO->transmissionType == (uint8_t)CO_PDO_TRANSM_TYPE_SYNC_ACYCLIC) {
                if (TPDO->sendRequest) {
                    (void)CO_TPDOsend(TPDO);
                }
            }
            /* send synchronous cyclic TPDO */
            else {
                /* is the start of synchronous TPDO transmission */
                if (TPDO->syncCounter == 255U) {
                    if ((TPDO->SYNC->counterOverflowValue != 0U) && (TPDO->syncStartValue != 0U)) {
                        /* syncStartValue is in use */
                        TPDO->syncCounter = 254;
                    } else {
                        /* Send first TPDO somewhere in the middle */
                        TPDO->syncCounter = (TPDO->transmissionType / 2U) + 1U;
                    }
                }
                /* If the syncStartValue is in use, start first TPDO after SYNC with matched syncStartValue. */
                if (TPDO->syncCounter == 254U) {
                    if (TPDO->SYNC->counter == TPDO->syncStartValue) {
                        TPDO->syncCounter = TPDO->transmissionType;
                        (void)CO_TPDOsend(TPDO);
                    }
                }
                /* Send TPDO after every N-th Sync */
                else if (--TPDO->syncCounter == 0U) {
                    TPDO->syncCounter = TPDO->transmissionType;
                    (void)CO_TPDOsend(TPDO);
                } else { /* MISRA C 2004 14.10 */
                }
            }
        }      /* else if (TPDO->SYNC && syncWas) */
        else { /* MISRA C 2004 14.10 */
        }
#endif

    } else {
        /* Not operational or valid, reset triggers */
        TPDO->sendRequest = true;
#if ((CO_CONFIG_PDO)&CO_CONFIG_TPDO_TIMERS_ENABLE) != 0
        TPDO->inhibitTimer = 0;
        TPDO->eventTimer = 0;
#endif
#if ((CO_CONFIG_PDO)&CO_CONFIG_PDO_SYNC_ENABLE) != 0
        TPDO->syncCounter = 255;
#endif
    }
}
#endif /* (CO_CONFIG_PDO) & CO_CONFIG_TPDO_ENABLE */
#endif /* (CO_CONFIG_PDO) & (CO_CONFIG_RPDO_ENABLE | CO_CONFIG_TPDO_ENABLE) */
