第一版代码,为了在EEPROM保存参数的时候走STM32的CRC,让Codex修改了一下,现在的效果是无法存储,codex表示原因是CRC方法不同,修改到一半今天的额度使用完了,有待后续解决CRC的bug
This commit is contained in:
720
Middleware/CANopenNode/301/CO_Emergency.c
Normal file
720
Middleware/CANopenNode/301/CO_Emergency.c
Normal file
@@ -0,0 +1,720 @@
|
||||
/*
|
||||
* CANopen Emergency object.
|
||||
*
|
||||
* @file CO_Emergency.c
|
||||
* @ingroup CO_Emergency
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2020 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_Emergency.h"
|
||||
|
||||
/* verify configuration */
|
||||
#if CO_CONFIG_EM_ERR_STATUS_BITS_COUNT < (6U * 8U) || CO_CONFIG_EM_ERR_STATUS_BITS_COUNT > 256U \
|
||||
|| (CO_CONFIG_EM_ERR_STATUS_BITS_COUNT % 8U) != 0
|
||||
#error CO_CONFIG_EM_ERR_STATUS_BITS_COUNT is not correct
|
||||
#endif
|
||||
|
||||
/* fifo buffer example for fifoSize = 7 (actual capacity = 6) *
|
||||
* *
|
||||
* 0 * * * * *
|
||||
* 1 pp==wp fifoPpPtr fifoWrPtr * *
|
||||
* 2 * * * * *
|
||||
* 3 * * * fifoWrPtr *
|
||||
* 4 * fifoWrPtr fifoPpPtr fifoPpPtr *
|
||||
* 5 * * * * *
|
||||
* 6 * * * * *
|
||||
* *
|
||||
* nothing 3 bytes 4 bytes buffer *
|
||||
* to process to process to process full *
|
||||
******************************************************************************/
|
||||
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_PRODUCER) != 0
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_PROD_CONFIGURABLE) != 0
|
||||
/*
|
||||
* Custom functions for read/write OD object "COB-ID EMCY"
|
||||
*
|
||||
* For more information see file CO_ODinterface.h, OD_IO_t.
|
||||
*/
|
||||
static ODR_t
|
||||
OD_read_1014(OD_stream_t* stream, void* buf, OD_size_t count, OD_size_t* countRead) {
|
||||
if ((stream == NULL) || (stream->subIndex != 0U) || (buf == NULL) || (count < sizeof(uint32_t))
|
||||
|| (countRead == NULL)) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
CO_EM_t* em = (CO_EM_t*)stream->object;
|
||||
|
||||
uint16_t canId = (em->producerCanId == CO_CAN_ID_EMERGENCY) ? (CO_CAN_ID_EMERGENCY + em->nodeId)
|
||||
: em->producerCanId;
|
||||
uint32_t COB_IDEmergency32 = em->producerEnabled ? 0U : 0x80000000U;
|
||||
COB_IDEmergency32 |= canId;
|
||||
(void)CO_setUint32(buf, COB_IDEmergency32);
|
||||
|
||||
*countRead = sizeof(uint32_t);
|
||||
return ODR_OK;
|
||||
}
|
||||
|
||||
static ODR_t
|
||||
OD_write_1014(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
|
||||
if ((stream == NULL) || (stream->subIndex != 0U) || (buf == NULL) || (count != sizeof(uint32_t))
|
||||
|| (countWritten == NULL)) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
CO_EM_t* em = (CO_EM_t*)stream->object;
|
||||
|
||||
/* Verify written value. COB ID must not change, if emergency is enabled */
|
||||
uint32_t COB_IDEmergency32 = CO_getUint32(buf);
|
||||
uint16_t newCanId = (uint16_t)(COB_IDEmergency32 & 0x7FFU);
|
||||
uint16_t curCanId = (em->producerCanId == CO_CAN_ID_EMERGENCY) ? (CO_CAN_ID_EMERGENCY + em->nodeId)
|
||||
: em->producerCanId;
|
||||
bool_t newEnabled = ((COB_IDEmergency32 & 0x80000000U) == 0U) && (newCanId != 0U);
|
||||
if (((COB_IDEmergency32 & 0x7FFFF800U) != 0U) || CO_IS_RESTRICTED_CAN_ID(newCanId)
|
||||
|| ((em->producerEnabled && newEnabled) && (newCanId != curCanId))) {
|
||||
return ODR_INVALID_VALUE;
|
||||
}
|
||||
|
||||
/* store values. If default CAN-ID is used, then store only value of CO_CAN_ID_EMERGENCY without node id. */
|
||||
em->producerEnabled = newEnabled;
|
||||
em->producerCanId = (newCanId == ((uint16_t)CO_CAN_ID_EMERGENCY + em->nodeId)) ? CO_CAN_ID_EMERGENCY : newCanId;
|
||||
|
||||
/* configure emergency message CAN transmission */
|
||||
if (newEnabled) {
|
||||
em->CANtxBuff = CO_CANtxBufferInit(em->CANdevTx, em->CANdevTxIdx, newCanId, false, 8U, false);
|
||||
}
|
||||
|
||||
/* write value to the original location in the Object Dictionary */
|
||||
return OD_writeOriginal(stream, buf, count, countWritten);
|
||||
}
|
||||
#else
|
||||
/*
|
||||
* Custom functions for read/write OD object "COB-ID EMCY"
|
||||
*
|
||||
* For more information see file CO_ODinterface.h, OD_IO_t.
|
||||
*/
|
||||
static ODR_t
|
||||
OD_read_1014_default(OD_stream_t* stream, void* buf, OD_size_t count, OD_size_t* countRead) {
|
||||
if ((stream == NULL) || (stream->subIndex != 0U) || (buf == NULL) || (count < sizeof(uint32_t))
|
||||
|| (countRead == NULL)) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
CO_EM_t* em = (CO_EM_t*)stream->object;
|
||||
|
||||
uint32_t COB_IDEmergency32 = em->producerEnabled ? 0U : 0x80000000U;
|
||||
COB_IDEmergency32 |= CO_CAN_ID_EMERGENCY + (uint32_t)em->nodeId;
|
||||
(void)CO_setUint32(buf, COB_IDEmergency32);
|
||||
|
||||
*countRead = sizeof(uint32_t);
|
||||
return ODR_OK;
|
||||
}
|
||||
#endif /* (CO_CONFIG_EM) & CO_CONFIG_EM_PROD_CONFIGURABLE */
|
||||
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_PROD_INHIBIT) != 0
|
||||
/*
|
||||
* Custom function for writing OD object "Inhibit time EMCY"
|
||||
*
|
||||
* For more information see file CO_ODinterface.h, OD_IO_t.
|
||||
*/
|
||||
static ODR_t
|
||||
OD_write_1015(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
|
||||
if ((stream == NULL) || (stream->subIndex != 0U) || (buf == NULL) || (count != sizeof(uint16_t))
|
||||
|| (countWritten == NULL)) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
CO_EM_t* em = (CO_EM_t*)stream->object;
|
||||
|
||||
/* update object */
|
||||
em->inhibitEmTime_us = (uint32_t)CO_getUint16(buf) * 100U;
|
||||
em->inhibitEmTimer = 0;
|
||||
|
||||
/* write value to the original location in the Object Dictionary */
|
||||
return OD_writeOriginal(stream, buf, count, countWritten);
|
||||
}
|
||||
#endif /* (CO_CONFIG_EM) & CO_CONFIG_EM_PROD_INHIBIT */
|
||||
#endif /* (CO_CONFIG_EM) & CO_CONFIG_EM_PRODUCER */
|
||||
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_HISTORY) != 0
|
||||
/*
|
||||
* Custom functions for read/write OD object _OD_statusBits_, optional
|
||||
*
|
||||
* For more information see file CO_ODinterface.h, OD_IO_t.
|
||||
*/
|
||||
static ODR_t
|
||||
OD_read_1003(OD_stream_t* stream, void* buf, OD_size_t count, OD_size_t* countRead) {
|
||||
if ((stream == NULL) || (buf == NULL) || (countRead == NULL) || ((count < 4U) && (stream->subIndex > 0U))
|
||||
|| (count < 1U)) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
CO_EM_t* em = (CO_EM_t*)stream->object;
|
||||
|
||||
if (em->fifoSize < 2U) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
if (stream->subIndex == 0U) {
|
||||
(void)CO_setUint8(buf, em->fifoCount);
|
||||
|
||||
*countRead = sizeof(uint8_t);
|
||||
return ODR_OK;
|
||||
} else if (stream->subIndex <= em->fifoCount) {
|
||||
/* newest error is reported on subIndex 1 and is stored just behind
|
||||
* fifoWrPtr. Get correct index in FIFO buffer. */
|
||||
int16_t index = (int16_t)em->fifoWrPtr - (int16_t)stream->subIndex;
|
||||
if (index < 0) {
|
||||
index += (int16_t)em->fifoSize;
|
||||
} else if (index >= (int16_t)(em->fifoSize)) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
} else { /* MISRA C 2004 14.10 */
|
||||
}
|
||||
(void)CO_setUint32(buf, em->fifo[index].msg);
|
||||
|
||||
*countRead = sizeof(uint32_t);
|
||||
return ODR_OK;
|
||||
} else {
|
||||
return ODR_NO_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
static ODR_t
|
||||
OD_write_1003(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
|
||||
if ((stream == NULL) || (stream->subIndex != 0U) || (buf == NULL) || (count != 1U) || (countWritten == NULL)) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
if (CO_getUint8(buf) != 0U) {
|
||||
return ODR_INVALID_VALUE;
|
||||
}
|
||||
|
||||
CO_EM_t* em = (CO_EM_t*)stream->object;
|
||||
|
||||
/* clear error history */
|
||||
em->fifoCount = 0;
|
||||
|
||||
*countWritten = sizeof(uint8_t);
|
||||
return ODR_OK;
|
||||
}
|
||||
#endif /* (CO_CONFIG_EM) & CO_CONFIG_EM_HISTORY */
|
||||
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_STATUS_BITS) != 0
|
||||
/*
|
||||
* Custom functions for read/write OD object _OD_statusBits_, optional
|
||||
*
|
||||
* For more information see file CO_ODinterface.h, OD_IO_t.
|
||||
*/
|
||||
static ODR_t
|
||||
OD_read_statusBits(OD_stream_t* stream, void* buf, OD_size_t count, OD_size_t* countRead) {
|
||||
if ((stream == NULL) || (stream->subIndex != 0U) || (buf == NULL) || (countRead == NULL)) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
CO_EM_t* em = (CO_EM_t*)stream->object;
|
||||
|
||||
/* get MAX(errorStatusBitsSize, bufSize, ODsizeIndication) */
|
||||
OD_size_t countReadLocal = CO_CONFIG_EM_ERR_STATUS_BITS_COUNT / 8U;
|
||||
if (countReadLocal > count) {
|
||||
countReadLocal = count;
|
||||
}
|
||||
if ((stream->dataLength != 0U) && (countReadLocal > stream->dataLength)) {
|
||||
countReadLocal = stream->dataLength;
|
||||
} else {
|
||||
stream->dataLength = countReadLocal;
|
||||
}
|
||||
|
||||
(void)memcpy((void*)(buf), (const void*)(&em->errorStatusBits[0]), countReadLocal);
|
||||
|
||||
*countRead = countReadLocal;
|
||||
return ODR_OK;
|
||||
}
|
||||
|
||||
static ODR_t
|
||||
OD_write_statusBits(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
|
||||
if ((stream == NULL) || (stream->subIndex != 0U) || (buf == NULL) || (countWritten == NULL)) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
CO_EM_t* em = (CO_EM_t*)stream->object;
|
||||
|
||||
/* get MAX(errorStatusBitsSize, bufSize, ODsizeIndication) */
|
||||
OD_size_t countWrite = CO_CONFIG_EM_ERR_STATUS_BITS_COUNT / 8U;
|
||||
if (countWrite > count) {
|
||||
countWrite = count;
|
||||
}
|
||||
if ((stream->dataLength != 0U) && (countWrite > stream->dataLength)) {
|
||||
countWrite = stream->dataLength;
|
||||
} else {
|
||||
stream->dataLength = countWrite;
|
||||
}
|
||||
|
||||
(void)memcpy((void*)(&em->errorStatusBits[0]), (const void*)(buf), countWrite);
|
||||
|
||||
*countWritten = countWrite;
|
||||
return ODR_OK;
|
||||
}
|
||||
#endif /* (CO_CONFIG_EM) & CO_CONFIG_EM_STATUS_BITS */
|
||||
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_CONSUMER) != 0
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
static void
|
||||
CO_EM_receive(void* object, void* msg) {
|
||||
CO_EM_t* em = (CO_EM_t*)object;
|
||||
|
||||
if ((em != NULL) && (em->pFunctSignalRx != NULL)) {
|
||||
uint16_t ident = CO_CANrxMsg_readIdent(msg);
|
||||
|
||||
/* ignore sync messages (necessary if sync object is not used) */
|
||||
if (ident != 0x80U) {
|
||||
const uint8_t* data = CO_CANrxMsg_readData(msg);
|
||||
uint16_t errorCode;
|
||||
uint32_t infoCode;
|
||||
|
||||
(void)memcpy((void*)(&errorCode), (const void*)(&data[0]), sizeof(errorCode));
|
||||
(void)memcpy((void*)(&infoCode), (const void*)(&data[4]), sizeof(infoCode));
|
||||
em->pFunctSignalRx(ident, CO_SWAP_16(errorCode), data[2], data[3], CO_SWAP_32(infoCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
CO_ReturnError_t
|
||||
CO_EM_init(CO_EM_t* em, CO_CANmodule_t* CANdevTx, const OD_entry_t* OD_1001_errReg,
|
||||
#if (((CO_CONFIG_EM) & (CO_CONFIG_EM_PRODUCER | CO_CONFIG_EM_HISTORY)) != 0) || defined CO_DOXYGEN
|
||||
CO_EM_fifo_t* fifo, uint8_t fifoSize,
|
||||
#endif
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_EM_PRODUCER) != 0) || defined CO_DOXYGEN
|
||||
OD_entry_t* OD_1014_cobIdEm, uint16_t CANdevTxIdx,
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_EM_PROD_INHIBIT) != 0) || defined CO_DOXYGEN
|
||||
OD_entry_t* OD_1015_InhTime,
|
||||
#endif
|
||||
#endif
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_EM_HISTORY) != 0) || defined CO_DOXYGEN
|
||||
OD_entry_t* OD_1003_preDefErr,
|
||||
#endif
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_EM_STATUS_BITS) != 0) || defined CO_DOXYGEN
|
||||
OD_entry_t* OD_statusBits,
|
||||
#endif
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_EM_CONSUMER) != 0) || defined CO_DOXYGEN
|
||||
CO_CANmodule_t* CANdevRx, uint16_t CANdevRxIdx,
|
||||
#endif
|
||||
const uint8_t nodeId, uint32_t* errInfo) {
|
||||
(void)nodeId; /* may be unused */
|
||||
CO_ReturnError_t ret = CO_ERROR_NO;
|
||||
|
||||
/* verify arguments */
|
||||
if ((em == NULL) || (OD_1001_errReg == NULL)
|
||||
#if ((CO_CONFIG_EM) & (CO_CONFIG_EM_PRODUCER | CO_CONFIG_EM_HISTORY)) != 0
|
||||
|| ((fifo == NULL) && (fifoSize >= 2U))
|
||||
#endif
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_PRODUCER) != 0
|
||||
|| (OD_1014_cobIdEm == NULL) || (CANdevTx == NULL) || (nodeId < 1U) || (nodeId > 127U)
|
||||
#endif
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_HISTORY) != 0
|
||||
|| (OD_1003_preDefErr == NULL)
|
||||
#endif
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_CONSUMER) != 0
|
||||
|| (CANdevRx == NULL)
|
||||
#endif
|
||||
) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* clear the object */
|
||||
(void)memset(em, 0, sizeof(CO_EM_t));
|
||||
|
||||
/* set object variables */
|
||||
em->CANdevTx = CANdevTx;
|
||||
|
||||
/* get and verify "Error register" from Object Dictionary */
|
||||
em->errorRegister = OD_getPtr(OD_1001_errReg, 0, sizeof(uint8_t), NULL);
|
||||
if (em->errorRegister == NULL) {
|
||||
if (errInfo != NULL) {
|
||||
*errInfo = OD_getIndex(OD_1001_errReg);
|
||||
}
|
||||
return CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
*em->errorRegister = 0;
|
||||
|
||||
#if ((CO_CONFIG_EM) & (CO_CONFIG_EM_PRODUCER | CO_CONFIG_EM_HISTORY)) != 0
|
||||
em->fifo = fifo;
|
||||
em->fifoSize = fifoSize;
|
||||
#endif
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_PRODUCER) != 0
|
||||
/* get initial and verify "COB-ID EMCY" from Object Dictionary */
|
||||
uint32_t COB_IDEmergency32;
|
||||
ODR_t odRet;
|
||||
odRet = OD_get_u32(OD_1014_cobIdEm, 0, &COB_IDEmergency32, true);
|
||||
if ((odRet != ODR_OK) || ((COB_IDEmergency32 & 0x7FFFF800U) != 0U)) {
|
||||
if (errInfo != NULL) {
|
||||
*errInfo = OD_getIndex(OD_1014_cobIdEm);
|
||||
}
|
||||
/* don't break a program, if only value of a parameter is wrong */
|
||||
if (odRet != ODR_OK) {
|
||||
return CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_PROD_CONFIGURABLE) != 0
|
||||
uint16_t producerCanId = (uint16_t)(COB_IDEmergency32 & 0x7FFU);
|
||||
em->producerEnabled = ((COB_IDEmergency32 & 0x80000000U) == 0U) && (producerCanId != 0U);
|
||||
|
||||
em->OD_1014_extension.object = em;
|
||||
em->OD_1014_extension.read = OD_read_1014;
|
||||
em->OD_1014_extension.write = OD_write_1014;
|
||||
odRet = OD_extension_init(OD_1014_cobIdEm, &em->OD_1014_extension);
|
||||
if (odRet != ODR_OK) {
|
||||
if (errInfo != NULL) {
|
||||
*errInfo = OD_getIndex(OD_1014_cobIdEm);
|
||||
}
|
||||
return CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
/* following two variables are used inside OD_read_1014 and OD_write_1014 */
|
||||
em->producerCanId = producerCanId;
|
||||
em->CANdevTxIdx = CANdevTxIdx;
|
||||
/* if default producerCanId is used, then value of CO_CAN_ID_EMERGENCY (0x80) is stored into non-volatile
|
||||
* memory. In that case it is necessary to add nodeId of this node to the stored value. */
|
||||
if (producerCanId == CO_CAN_ID_EMERGENCY) {
|
||||
producerCanId += nodeId;
|
||||
}
|
||||
#else
|
||||
uint16_t producerCanId = CO_CAN_ID_EMERGENCY + (uint16_t)nodeId;
|
||||
em->producerEnabled = (COB_IDEmergency32 & 0x80000000U) == 0U;
|
||||
|
||||
em->OD_1014_extension.object = em;
|
||||
em->OD_1014_extension.read = OD_read_1014_default;
|
||||
em->OD_1014_extension.write = OD_writeOriginal;
|
||||
odRet = OD_extension_init(OD_1014_cobIdEm, &em->OD_1014_extension);
|
||||
if (odRet != ODR_OK) {
|
||||
if (errInfo != NULL) {
|
||||
*errInfo = OD_getIndex(OD_1014_cobIdEm);
|
||||
}
|
||||
return CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* configure parameters and emergency message CAN transmission */
|
||||
em->nodeId = nodeId;
|
||||
|
||||
em->CANtxBuff = CO_CANtxBufferInit(CANdevTx, CANdevTxIdx, producerCanId, false, 8U, false);
|
||||
|
||||
if (em->CANtxBuff == NULL) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_PROD_INHIBIT) != 0
|
||||
/* get and verify optional "Inhibit time EMCY" from Object Dictionary */
|
||||
em->inhibitEmTime_us = 0;
|
||||
em->inhibitEmTimer = 0;
|
||||
uint16_t inhibitTime_100us;
|
||||
odRet = OD_get_u16(OD_1015_InhTime, 0, &inhibitTime_100us, true);
|
||||
if (odRet == ODR_OK) {
|
||||
em->inhibitEmTime_us = (uint32_t)inhibitTime_100us * 100U;
|
||||
|
||||
em->OD_1015_extension.object = em;
|
||||
em->OD_1015_extension.read = OD_readOriginal;
|
||||
em->OD_1015_extension.write = OD_write_1015;
|
||||
(void)OD_extension_init(OD_1015_InhTime, &em->OD_1015_extension);
|
||||
}
|
||||
#endif /* (CO_CONFIG_EM) & CO_CONFIG_EM_PROD_INHIBIT */
|
||||
#endif /* (CO_CONFIG_EM) & CO_CONFIG_EM_PRODUCER */
|
||||
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_HISTORY) != 0
|
||||
/* If OD entry available, make access to em->preDefErr */
|
||||
em->OD_1003_extension.object = em;
|
||||
em->OD_1003_extension.read = OD_read_1003;
|
||||
em->OD_1003_extension.write = OD_write_1003;
|
||||
(void)OD_extension_init(OD_1003_preDefErr, &em->OD_1003_extension);
|
||||
#endif /* (CO_CONFIG_EM) & CO_CONFIG_EM_HISTORY */
|
||||
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_STATUS_BITS) != 0
|
||||
/* If OD entry available, make access to em->errorStatusBits */
|
||||
em->OD_statusBits_extension.object = em;
|
||||
em->OD_statusBits_extension.read = OD_read_statusBits;
|
||||
em->OD_statusBits_extension.write = OD_write_statusBits;
|
||||
(void)OD_extension_init(OD_statusBits, &em->OD_statusBits_extension);
|
||||
#endif /* (CO_CONFIG_EM) & CO_CONFIG_EM_STATUS_BITS */
|
||||
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_CONSUMER) != 0
|
||||
em->pFunctSignalRx = NULL;
|
||||
/* configure SDO server CAN reception */
|
||||
ret = CO_CANrxBufferInit(CANdevRx, CANdevRxIdx, CO_CAN_ID_EMERGENCY, 0x780, false, (void*)em, CO_EM_receive);
|
||||
#endif /* (CO_CONFIG_EM) & CO_CONFIG_EM_CONSUMER */
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_CONSUMER) != 0
|
||||
void
|
||||
CO_EM_initCallbackRx(CO_EM_t* em,
|
||||
void (*pFunctSignalRx)(const uint16_t ident, const uint16_t errorCode, const uint8_t errorRegister,
|
||||
const uint8_t errorBit, const uint32_t infoCode)) {
|
||||
if (em != NULL) {
|
||||
em->pFunctSignalRx = pFunctSignalRx;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0
|
||||
void
|
||||
CO_EM_initCallbackPre(CO_EM_t* em, void* object, void (*pFunctSignal)(void* object)) {
|
||||
if (em != NULL) {
|
||||
em->functSignalObjectPre = object;
|
||||
em->pFunctSignalPre = pFunctSignal;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
CO_EM_process(CO_EM_t* em, bool_t NMTisPreOrOperational, uint32_t timeDifference_us, uint32_t* timerNext_us) {
|
||||
(void)timerNext_us; /* may be unused */
|
||||
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_PROD_INHIBIT) == 0
|
||||
(void)timeDifference_us; /* may be unused */
|
||||
#endif
|
||||
|
||||
/* verify errors from driver */
|
||||
uint16_t CANerrSt = em->CANdevTx->CANerrorStatus;
|
||||
if (CANerrSt != em->CANerrorStatusOld) {
|
||||
uint16_t CANerrStChanged = CANerrSt ^ em->CANerrorStatusOld;
|
||||
em->CANerrorStatusOld = CANerrSt;
|
||||
|
||||
if ((CANerrStChanged & (CO_CAN_ERRTX_WARNING | CO_CAN_ERRRX_WARNING)) != 0U) {
|
||||
CO_error(em, (CANerrSt & (CO_CAN_ERRTX_WARNING | CO_CAN_ERRRX_WARNING)) != 0U, CO_EM_CAN_BUS_WARNING,
|
||||
CO_EMC_NO_ERROR, 0);
|
||||
}
|
||||
if ((CANerrStChanged & CO_CAN_ERRTX_PASSIVE) != 0U) {
|
||||
CO_error(em, (CANerrSt & CO_CAN_ERRTX_PASSIVE) != 0U, CO_EM_CAN_TX_BUS_PASSIVE, CO_EMC_CAN_PASSIVE, 0);
|
||||
}
|
||||
if ((CANerrStChanged & CO_CAN_ERRTX_BUS_OFF) != 0U) {
|
||||
CO_error(em, (CANerrSt & CO_CAN_ERRTX_BUS_OFF) != 0U, CO_EM_CAN_TX_BUS_OFF, CO_EMC_BUS_OFF_RECOVERED, 0);
|
||||
}
|
||||
if ((CANerrStChanged & CO_CAN_ERRTX_OVERFLOW) != 0U) {
|
||||
CO_error(em, (CANerrSt & CO_CAN_ERRTX_OVERFLOW) != 0U, CO_EM_CAN_TX_OVERFLOW, CO_EMC_CAN_OVERRUN, 0);
|
||||
}
|
||||
if ((CANerrStChanged & CO_CAN_ERRTX_PDO_LATE) != 0U) {
|
||||
CO_error(em, (CANerrSt & CO_CAN_ERRTX_PDO_LATE) != 0U, CO_EM_TPDO_OUTSIDE_WINDOW, CO_EMC_COMMUNICATION, 0);
|
||||
}
|
||||
if ((CANerrStChanged & CO_CAN_ERRRX_PASSIVE) != 0U) {
|
||||
CO_error(em, (CANerrSt & CO_CAN_ERRRX_PASSIVE) != 0U, CO_EM_CAN_RX_BUS_PASSIVE, CO_EMC_CAN_PASSIVE, 0);
|
||||
}
|
||||
if ((CANerrStChanged & CO_CAN_ERRRX_OVERFLOW) != 0U) {
|
||||
CO_error(em, (CANerrSt & CO_CAN_ERRRX_OVERFLOW) != 0U, CO_EM_CAN_RXB_OVERFLOW, CO_EMC_CAN_OVERRUN, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* calculate Error register */
|
||||
uint8_t errorRegister = 0U;
|
||||
if (CO_CONFIG_ERR_CONDITION_GENERIC) {
|
||||
errorRegister |= (uint8_t)CO_ERR_REG_GENERIC_ERR;
|
||||
}
|
||||
#ifdef CO_CONFIG_ERR_CONDITION_CURRENT
|
||||
if (CO_CONFIG_ERR_CONDITION_CURRENT) {
|
||||
errorRegister |= (uint8_t)CO_ERR_REG_CURRENT;
|
||||
}
|
||||
#endif
|
||||
#ifdef CO_CONFIG_ERR_CONDITION_VOLTAGE
|
||||
if (CO_CONFIG_ERR_CONDITION_VOLTAGE) {
|
||||
errorRegister |= (uint8_t)CO_ERR_REG_VOLTAGE;
|
||||
}
|
||||
#endif
|
||||
#ifdef CO_CONFIG_ERR_CONDITION_TEMPERATURE
|
||||
if (CO_CONFIG_ERR_CONDITION_TEMPERATURE) {
|
||||
errorRegister |= (uint8_t)CO_ERR_REG_TEMPERATURE;
|
||||
}
|
||||
#endif
|
||||
if (CO_CONFIG_ERR_CONDITION_COMMUNICATION) {
|
||||
errorRegister |= (uint8_t)CO_ERR_REG_COMMUNICATION;
|
||||
}
|
||||
#ifdef CO_CONFIG_ERR_CONDITION_DEV_PROFILE
|
||||
if (CO_CONFIG_ERR_CONDITION_DEV_PROFILE) {
|
||||
errorRegister |= (uint8_t)CO_ERR_REG_DEV_PROFILE;
|
||||
}
|
||||
#endif
|
||||
if (CO_CONFIG_ERR_CONDITION_MANUFACTURER) {
|
||||
errorRegister |= (uint8_t)CO_ERR_REG_MANUFACTURER;
|
||||
}
|
||||
*em->errorRegister = errorRegister;
|
||||
|
||||
if (!NMTisPreOrOperational) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* post-process Emergency message in fifo buffer. */
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_PRODUCER) != 0
|
||||
if (em->fifoSize >= 2U) {
|
||||
uint8_t fifoPpPtr = em->fifoPpPtr;
|
||||
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_PROD_INHIBIT) != 0
|
||||
if (em->inhibitEmTimer < em->inhibitEmTime_us) {
|
||||
em->inhibitEmTimer += timeDifference_us;
|
||||
}
|
||||
|
||||
if (!em->CANtxBuff->bufferFull && (fifoPpPtr != em->fifoWrPtr)
|
||||
&& (em->inhibitEmTimer >= em->inhibitEmTime_us)) {
|
||||
em->inhibitEmTimer = 0;
|
||||
#else
|
||||
if ((!em->CANtxBuff->bufferFull) && (fifoPpPtr != em->fifoWrPtr)) {
|
||||
#endif
|
||||
/* add error register to emergency message */
|
||||
em->fifo[fifoPpPtr].msg |= (uint32_t)errorRegister << 16;
|
||||
|
||||
/* send emergency message */
|
||||
(void)memcpy((void*)em->CANtxBuff->data, (void*)&em->fifo[fifoPpPtr].msg, sizeof(em->CANtxBuff->data));
|
||||
(void)CO_CANsend(em->CANdevTx, em->CANtxBuff);
|
||||
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_CONSUMER) != 0
|
||||
/* report also own emergency messages */
|
||||
if (em->pFunctSignalRx != NULL) {
|
||||
uint32_t errMsg = em->fifo[fifoPpPtr].msg;
|
||||
em->pFunctSignalRx(0, CO_SWAP_16((uint16_t)errMsg), errorRegister, (uint8_t)(errMsg >> 24),
|
||||
CO_SWAP_32(em->fifo[fifoPpPtr].info));
|
||||
}
|
||||
#endif
|
||||
|
||||
/* increment pointer */
|
||||
fifoPpPtr++;
|
||||
em->fifoPpPtr = (fifoPpPtr < em->fifoSize) ? fifoPpPtr : 0U;
|
||||
|
||||
/* verify message buffer overflow. Clear error condition if all messages from fifo buffer are processed */
|
||||
if (em->fifoOverflow == 1U) {
|
||||
em->fifoOverflow = 2;
|
||||
CO_errorReport(em, CO_EM_EMERGENCY_BUFFER_FULL, CO_EMC_GENERIC, 0);
|
||||
} else if ((em->fifoOverflow == 2U) && (em->fifoPpPtr == em->fifoWrPtr)) {
|
||||
em->fifoOverflow = 0;
|
||||
CO_errorReset(em, CO_EM_EMERGENCY_BUFFER_FULL, 0);
|
||||
} else { /* MISRA C 2004 14.10 */
|
||||
}
|
||||
}
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_PROD_INHIBIT) != 0
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_FLAG_TIMERNEXT) != 0
|
||||
else if ((timerNext_us != NULL) && (em->inhibitEmTimer < em->inhibitEmTime_us)) {
|
||||
/* check again after inhibit time elapsed */
|
||||
uint32_t diff = em->inhibitEmTime_us - em->inhibitEmTimer;
|
||||
if (*timerNext_us > diff) {
|
||||
*timerNext_us = diff;
|
||||
}
|
||||
} else { /* MISRA C 2004 14.10 */
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#elif ((CO_CONFIG_EM)&CO_CONFIG_EM_HISTORY) != 0
|
||||
if (em->fifoSize >= 2) {
|
||||
uint8_t fifoPpPtr = em->fifoPpPtr;
|
||||
while (fifoPpPtr != em->fifoWrPtr) {
|
||||
/* add error register to emergency message and increment pointers */
|
||||
em->fifo[fifoPpPtr].msg |= (uint32_t)errorRegister << 16;
|
||||
|
||||
if (++fifoPpPtr >= em->fifoSize) {
|
||||
fifoPpPtr = 0;
|
||||
}
|
||||
}
|
||||
em->fifoPpPtr = fifoPpPtr;
|
||||
}
|
||||
#endif /* (CO_CONFIG_EM) & CO_CONFIG_EM_PRODUCER, #elif CO_CONFIG_EM_HISTORY */
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
CO_error(CO_EM_t* em, bool_t setError, const uint8_t errorBit, uint16_t errorCode, uint32_t infoCode) {
|
||||
if (em == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t index = errorBit >> 3;
|
||||
uint8_t bitmask = 1U << (errorBit & 0x7U);
|
||||
|
||||
/* if unsupported errorBit, change to 'CO_EM_WRONG_ERROR_REPORT' */
|
||||
if (index >= (CO_CONFIG_EM_ERR_STATUS_BITS_COUNT / 8U)) {
|
||||
index = CO_EM_WRONG_ERROR_REPORT >> 3;
|
||||
bitmask = 1U << (CO_EM_WRONG_ERROR_REPORT & 0x7U);
|
||||
errorCode = CO_EMC_SOFTWARE_INTERNAL;
|
||||
infoCode = errorBit;
|
||||
}
|
||||
|
||||
uint8_t* errorStatusBits = &em->errorStatusBits[index];
|
||||
uint8_t errorStatusBitMasked = *errorStatusBits & bitmask;
|
||||
|
||||
/* If error is already set (or unset), return without further actions,
|
||||
* otherwise toggle bit and continue with error indication. */
|
||||
if (setError) {
|
||||
if (errorStatusBitMasked != 0U) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (errorStatusBitMasked == 0U) {
|
||||
return;
|
||||
}
|
||||
errorCode = CO_EMC_NO_ERROR;
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_EM) & (CO_CONFIG_EM_PRODUCER | CO_CONFIG_EM_HISTORY)) != 0
|
||||
/* prepare emergency message. Error register will be added in post-process */
|
||||
uint32_t errMsg = ((uint32_t)errorBit << 24) | CO_SWAP_16(errorCode);
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_PRODUCER) != 0
|
||||
uint32_t infoCodeSwapped = CO_SWAP_32(infoCode);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* safely write data, and increment pointers */
|
||||
CO_LOCK_EMCY(em->CANdevTx);
|
||||
if (setError) {
|
||||
*errorStatusBits |= bitmask;
|
||||
} else {
|
||||
*errorStatusBits &= ~bitmask;
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_EM) & (CO_CONFIG_EM_PRODUCER | CO_CONFIG_EM_HISTORY)) != 0
|
||||
if (em->fifoSize >= 2U) {
|
||||
uint8_t fifoWrPtr = em->fifoWrPtr;
|
||||
uint8_t fifoWrPtrNext = fifoWrPtr + 1U;
|
||||
if (fifoWrPtrNext >= em->fifoSize) {
|
||||
fifoWrPtrNext = 0;
|
||||
}
|
||||
|
||||
if (fifoWrPtrNext == em->fifoPpPtr) {
|
||||
em->fifoOverflow = 1;
|
||||
} else {
|
||||
em->fifo[fifoWrPtr].msg = errMsg;
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_PRODUCER) != 0
|
||||
em->fifo[fifoWrPtr].info = infoCodeSwapped;
|
||||
#endif
|
||||
em->fifoWrPtr = fifoWrPtrNext;
|
||||
if (em->fifoCount < (em->fifoSize - 1U)) {
|
||||
em->fifoCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* (CO_CONFIG_EM) & (CO_CONFIG_EM_PRODUCER | CO_CONFIG_EM_HISTORY) */
|
||||
|
||||
CO_UNLOCK_EMCY(em->CANdevTx);
|
||||
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0
|
||||
#if ((CO_CONFIG_EM)&CO_CONFIG_EM_PRODUCER) != 0
|
||||
/* Optional signal to RTOS, which can resume task, which handles CO_EM_process */
|
||||
if ((em->pFunctSignalPre != NULL) && em->producerEnabled) {
|
||||
em->pFunctSignalPre(em->functSignalObjectPre);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
482
Middleware/CANopenNode/301/CO_Emergency.h
Normal file
482
Middleware/CANopenNode/301/CO_Emergency.h
Normal file
@@ -0,0 +1,482 @@
|
||||
/**
|
||||
* CANopen Emergency protocol.
|
||||
*
|
||||
* @file CO_Emergency.h
|
||||
* @ingroup CO_Emergency
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2020 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.
|
||||
*/
|
||||
|
||||
#ifndef CO_EMERGENCY_H
|
||||
#define CO_EMERGENCY_H
|
||||
|
||||
#include "301/CO_driver.h"
|
||||
#include "301/CO_ODinterface.h"
|
||||
|
||||
/* default configuration, see CO_config.h */
|
||||
#ifndef CO_CONFIG_EM
|
||||
#define CO_CONFIG_EM \
|
||||
(CO_CONFIG_EM_PRODUCER | CO_CONFIG_EM_HISTORY | CO_CONFIG_GLOBAL_FLAG_CALLBACK_PRE \
|
||||
| CO_CONFIG_GLOBAL_FLAG_TIMERNEXT)
|
||||
#endif
|
||||
#ifndef CO_CONFIG_EM_ERR_STATUS_BITS_COUNT
|
||||
#define CO_CONFIG_EM_ERR_STATUS_BITS_COUNT (10U * 8U)
|
||||
#endif
|
||||
#ifndef CO_CONFIG_ERR_CONDITION_GENERIC
|
||||
#define CO_CONFIG_ERR_CONDITION_GENERIC (em->errorStatusBits[5] != 0U)
|
||||
#endif
|
||||
#ifndef CO_CONFIG_ERR_CONDITION_COMMUNICATION
|
||||
#define CO_CONFIG_ERR_CONDITION_COMMUNICATION ((em->errorStatusBits[2] != 0U) || (em->errorStatusBits[3] != 0U))
|
||||
#endif
|
||||
#ifndef CO_CONFIG_ERR_CONDITION_MANUFACTURER
|
||||
#define CO_CONFIG_ERR_CONDITION_MANUFACTURER ((em->errorStatusBits[8] != 0U) || (em->errorStatusBits[9] != 0U))
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_Emergency Emergency
|
||||
* CANopen Emergency protocol.
|
||||
*
|
||||
* @ingroup CO_CANopen_301
|
||||
* @{
|
||||
* Error control and Emergency is used for control internal error state and for sending a CANopen Emergency message.
|
||||
*
|
||||
* In case of error condition stack or application calls CO_errorReport() function with indication of the error.
|
||||
* Specific error condition is reported (with CANopen Emergency message) only the first time after it occurs. Internal
|
||||
* state of specific error condition is indicated by internal bitfield variable, with space for maximum @ref
|
||||
* CO_CONFIG_EM_ERR_STATUS_BITS_COUNT bits. Meaning for each bit is described by @ref CO_EM_errorStatusBits_t. Specific
|
||||
* error condition can be reset by CO_errorReset() function. In that case Emergency message is sent with CO_EM_NO_ERROR
|
||||
* indication.
|
||||
*
|
||||
* Some error conditions are informative and some are critical. Critical error conditions set the corresponding bit in
|
||||
* @ref CO_errorRegister_t. Critical error conditions for generic error are specified by @ref
|
||||
* CO_CONFIG_ERR_CONDITION_GENERIC macro. Similar macros are defined for other error bits in in @ref CO_errorRegister_t.
|
||||
*
|
||||
* ### Emergency producer
|
||||
* If @ref CO_CONFIG_EM has CO_CONFIG_EM_PRODUCER enabled, then CANopen Emergency message will be sent on each change of
|
||||
* any error condition. Emergency message contents are:
|
||||
*
|
||||
* Byte | Description
|
||||
* -----|-----------------------------------------------------------
|
||||
* 0..1 | @ref CO_EM_errorCode_t
|
||||
* 2 | @ref CO_errorRegister_t
|
||||
* 3 | Index of error condition (see @ref CO_EM_errorStatusBits_t).
|
||||
* 4..7 | Additional informative argument to CO_errorReport() function.
|
||||
*
|
||||
* ### Error history
|
||||
* If @ref CO_CONFIG_EM has CO_CONFIG_EM_HISTORY enabled, then latest errors can be read from _Pre Defined Error Field_
|
||||
* (object dictionary, index 0x1003). Contents corresponds to bytes 0..3 from the Emergency message.
|
||||
*
|
||||
* ### Emergency consumer
|
||||
* If @ref CO_CONFIG_EM has CO_CONFIG_EM_CONSUMER enabled, then callback can be registered by @ref
|
||||
* CO_EM_initCallbackRx() function.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup CO_errorRegister_t CANopen Error register
|
||||
* @{
|
||||
*
|
||||
* Mandatory for CANopen, resides in object dictionary, index 0x1001.
|
||||
*
|
||||
* Error register is calculated from internal bitfield variable, critical bits. See @ref CO_EM_errorStatusBits_t and
|
||||
* @ref CO_STACK_CONFIG_EMERGENCY for error condition macros.
|
||||
*
|
||||
* Internal errors may prevent device to stay in NMT Operational state and changes may switch between the states. See
|
||||
* @ref CO_NMT_control_t for details.
|
||||
*/
|
||||
#define CO_ERR_REG_GENERIC_ERR 0x01U /**< bit 0, generic error */
|
||||
#define CO_ERR_REG_CURRENT 0x02U /**< bit 1, current */
|
||||
#define CO_ERR_REG_VOLTAGE 0x04U /**< bit 2, voltage */
|
||||
#define CO_ERR_REG_TEMPERATURE 0x08U /**< bit 3, temperature */
|
||||
#define CO_ERR_REG_COMMUNICATION 0x10U /**< bit 4, communication error */
|
||||
#define CO_ERR_REG_DEV_PROFILE 0x20U /**< bit 5, device profile specific */
|
||||
#define CO_ERR_REG_RESERVED 0x40U /**< bit 6, reserved (always 0) */
|
||||
#define CO_ERR_REG_MANUFACTURER 0x80U /**< bit 7, manufacturer specific */
|
||||
|
||||
/** @} */ /* CO_errorRegister_t */
|
||||
|
||||
/**
|
||||
* @defgroup CO_EM_errorCode_t CANopen Error code
|
||||
* @{
|
||||
*
|
||||
* Standard error codes according to CiA DS-301 and DS-401.
|
||||
*/
|
||||
#define CO_EMC_NO_ERROR 0x0000U /**< 0x00xx error Reset or No Error */
|
||||
#define CO_EMC_GENERIC 0x1000U /**< 0x10xx Generic Error */
|
||||
#define CO_EMC_CURRENT 0x2000U /**< 0x20xx Current */
|
||||
#define CO_EMC_CURRENT_INPUT 0x2100U /**< 0x21xx Current device input side */
|
||||
#define CO_EMC_CURRENT_INSIDE 0x2200U /**< 0x22xx Current inside the device */
|
||||
#define CO_EMC_CURRENT_OUTPUT 0x2300U /**< 0x23xx Current device output side */
|
||||
#define CO_EMC_VOLTAGE 0x3000U /**< 0x30xx Voltage */
|
||||
#define CO_EMC_VOLTAGE_MAINS 0x3100U /**< 0x31xx Mains Voltage */
|
||||
#define CO_EMC_VOLTAGE_INSIDE 0x3200U /**< 0x32xx Voltage inside the device */
|
||||
#define CO_EMC_VOLTAGE_OUTPUT 0x3300U /**< 0x33xx Output Voltage */
|
||||
#define CO_EMC_TEMPERATURE 0x4000U /**< 0x40xx Temperature */
|
||||
#define CO_EMC_TEMP_AMBIENT 0x4100U /**< 0x41xx Ambient Temperature */
|
||||
#define CO_EMC_TEMP_DEVICE 0x4200U /**< 0x42xx Device Temperature */
|
||||
#define CO_EMC_HARDWARE 0x5000U /**< 0x50xx Device Hardware */
|
||||
#define CO_EMC_SOFTWARE_DEVICE 0x6000U /**< 0x60xx Device Software */
|
||||
#define CO_EMC_SOFTWARE_INTERNAL 0x6100U /**< 0x61xx Internal Software */
|
||||
#define CO_EMC_SOFTWARE_USER 0x6200U /**< 0x62xx User Software */
|
||||
#define CO_EMC_DATA_SET 0x6300U /**< 0x63xx Data Set */
|
||||
#define CO_EMC_ADDITIONAL_MODUL 0x7000U /**< 0x70xx Additional Modules */
|
||||
#define CO_EMC_MONITORING 0x8000U /**< 0x80xx Monitoring */
|
||||
#define CO_EMC_COMMUNICATION 0x8100U /**< 0x81xx Communication */
|
||||
#define CO_EMC_CAN_OVERRUN 0x8110U /**< 0x8110 CAN Overrun (Objects lost) */
|
||||
#define CO_EMC_CAN_PASSIVE 0x8120U /**< 0x8120 CAN in Error Passive Mode */
|
||||
#define CO_EMC_HEARTBEAT 0x8130U /**< 0x8130 Life Guard Error or Heartbeat Error */
|
||||
#define CO_EMC_BUS_OFF_RECOVERED 0x8140U /**< 0x8140 recovered from bus off */
|
||||
#define CO_EMC_CAN_ID_COLLISION 0x8150U /**< 0x8150 CAN-ID collision */
|
||||
#define CO_EMC_PROTOCOL_ERROR 0x8200U /**< 0x82xx Protocol Error */
|
||||
#define CO_EMC_PDO_LENGTH 0x8210U /**< 0x8210 PDO not processed due to length error */
|
||||
#define CO_EMC_PDO_LENGTH_EXC 0x8220U /**< 0x8220 PDO length exceeded */
|
||||
#define CO_EMC_DAM_MPDO 0x8230U /**< 0x8230 DAM MPDO not processed destination object not available */
|
||||
#define CO_EMC_SYNC_DATA_LENGTH 0x8240U /**< 0x8240 Unexpected SYNC data length */
|
||||
#define CO_EMC_RPDO_TIMEOUT 0x8250U /**< 0x8250 RPDO timeout */
|
||||
#define CO_EMC_EXTERNAL_ERROR 0x9000U /**< 0x90xx External Error */
|
||||
#define CO_EMC_ADDITIONAL_FUNC 0xF000U /**< 0xF0xx Additional Functions */
|
||||
#define CO_EMC_DEVICE_SPECIFIC 0xFF00U /**< 0xFFxx Device specific */
|
||||
|
||||
#define CO_EMC401_OUT_CUR_HI 0x2310U /**< 0x2310 DS401 Current at outputs too high (overload) */
|
||||
#define CO_EMC401_OUT_SHORTED 0x2320U /**< 0x2320 DS401 Short circuit at outputs */
|
||||
#define CO_EMC401_OUT_LOAD_DUMP 0x2330U /**< 0x2330 DS401 Load dump at outputs */
|
||||
#define CO_EMC401_IN_VOLT_HI 0x3110U /**< 0x3110 DS401 Input voltage too high */
|
||||
#define CO_EMC401_IN_VOLT_LOW 0x3120U /**< 0x3120 DS401 Input voltage too low */
|
||||
#define CO_EMC401_INTERN_VOLT_HI 0x3210U /**< 0x3210 DS401 Internal voltage too high */
|
||||
#define CO_EMC401_INTERN_VOLT_LO 0x3220U /**< 0x3220 DS401 Internal voltage too low */
|
||||
#define CO_EMC401_OUT_VOLT_HIGH 0x3310U /**< 0x3310 DS401 Output voltage too high */
|
||||
#define CO_EMC401_OUT_VOLT_LOW 0x3320U /**< 0x3320 DS401 Output voltage too low */
|
||||
|
||||
/** @} */ /* CO_EM_errorCode_t */
|
||||
|
||||
/**
|
||||
* @defgroup CO_EM_errorStatusBits_t Error status bits
|
||||
* @{
|
||||
*
|
||||
* Bits for internal indication of the error condition. Each error condition is specified by unique index from 0x00 up
|
||||
* to 0xFF.
|
||||
*
|
||||
* If specific error occurs in the stack or in the application, CO_errorReport() sets specific bit in the
|
||||
* _errorStatusBit_ variable from @ref CO_EM_t. If bit was already set, function returns without any action. Otherwise
|
||||
* it prepares emergency message.
|
||||
*
|
||||
* Maximum size (in bits) of the _errorStatusBit_ variable is specified by @ref CO_CONFIG_EM_ERR_STATUS_BITS_COUNT (set
|
||||
* to 10*8 bits by default). Stack uses first 6 bytes. Additional 4 bytes are pre-defined for manufacturer or device
|
||||
* specific error indications, by default.
|
||||
*/
|
||||
#define CO_EM_NO_ERROR 0x00U /**< 0x00 Error Reset or No Error */
|
||||
#define CO_EM_CAN_BUS_WARNING 0x01U /**< 0x01 communication info CAN bus warning limit reached */
|
||||
#define CO_EM_RXMSG_WRONG_LENGTH 0x02U /**< 0x02 communication info Wrong data length of the received CAN message */
|
||||
#define CO_EM_RXMSG_OVERFLOW 0x03U /**< 0x03 communication info Previous received CAN message wasn't processed */
|
||||
#define CO_EM_RPDO_WRONG_LENGTH 0x04U /**< 0x04 communication info Wrong data length of received PDO */
|
||||
#define CO_EM_RPDO_OVERFLOW 0x05U /**< 0x05 communication info Previous received PDO wasn't processed yet */
|
||||
#define CO_EM_CAN_RX_BUS_PASSIVE 0x06U /**< 0x06 communication info CAN receive bus is passive */
|
||||
#define CO_EM_CAN_TX_BUS_PASSIVE 0x07U /**< 0x07 communication info CAN transmit bus is passive */
|
||||
#define CO_EM_NMT_WRONG_COMMAND 0x08U /**< 0x08 communication info Wrong NMT command received */
|
||||
#define CO_EM_TIME_TIMEOUT 0x09U /**< 0x09 communication info TIME message timeout */
|
||||
#define CO_EM_0A_unused 0x0AU /**< 0x0A communication info (unused) */
|
||||
#define CO_EM_0B_unused 0x0BU /**< 0x0B communication info (unused) */
|
||||
#define CO_EM_0C_unused 0x0CU /**< 0x0C communication info (unused) */
|
||||
#define CO_EM_0D_unused 0x0DU /**< 0x0D communication info (unused) */
|
||||
#define CO_EM_0E_unused 0x0EU /**< 0x0E communication info (unused) */
|
||||
#define CO_EM_0F_unused 0x0FU /**< 0x0F communication info (unused) */
|
||||
|
||||
#define CO_EM_10_unused 0x10U /**< 0x10 communication critical (unused) */
|
||||
#define CO_EM_11_unused 0x11U /**< 0x11 communication critical (unused) */
|
||||
#define CO_EM_CAN_TX_BUS_OFF 0x12U /**< 0x12 communication critical CAN transmit bus is off */
|
||||
#define CO_EM_CAN_RXB_OVERFLOW 0x13U /**< 0x13 communication critical CAN module receive buffer overflowed */
|
||||
#define CO_EM_CAN_TX_OVERFLOW 0x14U /**< 0x14 communication critical CAN transmit buffer overflowed */
|
||||
#define CO_EM_TPDO_OUTSIDE_WINDOW 0x15U /**< 0x15 communication critical TPDO is outside SYNC window */
|
||||
#define CO_EM_16_unused 0x16U /**< 0x16 communication critical (unused) */
|
||||
#define CO_EM_RPDO_TIME_OUT 0x17U /**< 0x17 communication critical RPDO message timeout */
|
||||
#define CO_EM_SYNC_TIME_OUT 0x18U /**< 0x18 communication critical SYNC message timeout */
|
||||
#define CO_EM_SYNC_LENGTH 0x19U /**< 0x19 communication critical Unexpected SYNC data length */
|
||||
#define CO_EM_PDO_WRONG_MAPPING 0x1AU /**< 0x1A communication critical Error with PDO mapping */
|
||||
#define CO_EM_HEARTBEAT_CONSUMER 0x1BU /**< 0x1B communication critical Heartbeat consumer timeout */
|
||||
#define CO_EM_HB_CONSUMER_REMOTE_RESET 0x1CU /**< 0x1C comm. critical Heartbeat consumer detected remote node reset */
|
||||
#define CO_EM_SRDO_CONFIGURATION 0x1DU /**< 0x1D communication critical Error in SRDO configuration parameters */
|
||||
#define CO_EM_1E_unused 0x1EU /**< 0x1E communication critical (unused) */
|
||||
#define CO_EM_1F_unused 0x1FU /**< 0x1F communication critical (unused) */
|
||||
|
||||
#define CO_EM_EMERGENCY_BUFFER_FULL 0x20U /**< 0x20 generic info Emergency buffer is full or message wasn't sent */
|
||||
#define CO_EM_21_unused 0x21U /**< 0x21 generic info (unused) */
|
||||
#define CO_EM_MICROCONTROLLER_RESET 0x22U /**< 0x22 generic info Microcontroller has just started */
|
||||
#define CO_EM_23_unused 0x23U /**< 0x23 generic info (unused) */
|
||||
#define CO_EM_24_unused 0x24U /**< 0x24 generic info (unused) */
|
||||
#define CO_EM_25_unused 0x25U /**< 0x25 generic info (unused) */
|
||||
#define CO_EM_26_unused 0x26U /**< 0x26 generic info (unused) */
|
||||
#define CO_EM_NON_VOLATILE_AUTO_SAVE 0x27U /**< 0x27 generic info Automatic store to non-volatile memory failed */
|
||||
|
||||
#define CO_EM_WRONG_ERROR_REPORT 0x28U /**< 0x28 generic critical Wrong parameters to CO_errorReport() */
|
||||
#define CO_EM_ISR_TIMER_OVERFLOW 0x29U /**< 0x29 generic critical Timer task has overflowed */
|
||||
#define CO_EM_MEMORY_ALLOCATION_ERROR 0x2AU /**< 0x2A generic critical Unable to allocate memory for objects */
|
||||
#define CO_EM_GENERIC_ERROR 0x2BU /**< 0x2B generic critical Generic error test usage */
|
||||
#define CO_EM_GENERIC_SOFTWARE_ERROR 0x2CU /**< 0x2C generic critical Software error */
|
||||
#define CO_EM_INCONSISTENT_OBJECT_DICT 0x2DU /**< 0x2D generic critical Object dict. does not match the software */
|
||||
#define CO_EM_CALCULATION_OF_PARAMETERS 0x2EU /**< 0x2E generic critical Error in calculation of device parameters */
|
||||
#define CO_EM_NON_VOLATILE_MEMORY 0x2FU /**< 0x2F generic critical Error with access to non volatile memory */
|
||||
|
||||
/**
|
||||
* 0x30+ manufacturer info or critical Error status buts free to use by manufacturer. By default bits 0x30..0x3F are set
|
||||
* as informational and bits 0x40..0x4F are set as critical. Manufacturer critical bits sets the error register as
|
||||
* specified by @ref CO_CONFIG_ERR_CONDITION_MANUFACTURER
|
||||
*/
|
||||
#define CO_EM_MANUFACTURER_START 0x30U
|
||||
/** (@ref CO_CONFIG_EM_ERR_STATUS_BITS_COUNT - 1) largest value of the Error status bit. */
|
||||
#define CO_EM_MANUFACTURER_END (CO_CONFIG_EM_ERR_STATUS_BITS_COUNT - 1U)
|
||||
|
||||
/** @} */ /* CO_EM_errorStatusBits_t */
|
||||
|
||||
#if (((CO_CONFIG_EM) & (CO_CONFIG_EM_PRODUCER | CO_CONFIG_EM_HISTORY)) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Fifo buffer for emergency producer and error history
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t msg;
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_EM_PRODUCER) != 0) || defined CO_DOXYGEN
|
||||
uint32_t info;
|
||||
#endif
|
||||
} CO_EM_fifo_t;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Emergency object.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t errorStatusBits[CO_CONFIG_EM_ERR_STATUS_BITS_COUNT / 8U]; /**< Bitfield for the internal indication of
|
||||
the error condition. */
|
||||
uint8_t* errorRegister; /**< Pointer to error register in object dictionary at 0x1001,00. */
|
||||
uint16_t CANerrorStatusOld; /**< Old CAN error status bitfield */
|
||||
CO_CANmodule_t* CANdevTx; /**< From CO_EM_init() */
|
||||
|
||||
#if (((CO_CONFIG_EM) & (CO_CONFIG_EM_PRODUCER | CO_CONFIG_EM_HISTORY)) != 0) || defined CO_DOXYGEN
|
||||
CO_EM_fifo_t*
|
||||
fifo; /**< Internal circular FIFO buffer for storing pre-processed emergency messages. Messages are added by
|
||||
@ref CO_error() function. All messages are later post-processed by @ref CO_EM_process() function. In
|
||||
case of overflow, error is indicated but emergency message is not sent. Fifo is also used for error
|
||||
history, OD object 0x1003, "Pre-defined error field". Buffer is defined by @ref CO_EM_init(). */
|
||||
uint8_t fifoSize; /**< Size of the above buffer, specified by @ref CO_EM_init(). */
|
||||
uint8_t fifoWrPtr; /**< Pointer for the fifo buffer, where next emergency message will be written by @ref CO_error()
|
||||
function. */
|
||||
uint8_t fifoPpPtr; /**< Pointer for the fifo, where next emergency message has to be post-processed by @ref
|
||||
CO_EM_process() function. If equal to bufWrPtr, then all messages has been post-processed. */
|
||||
uint8_t fifoOverflow; /**< Indication of overflow - messages in buffer are not post-processed */
|
||||
uint8_t fifoCount; /**< Count of emergency messages in fifo, used for OD object 0x1003 */
|
||||
#endif /* (CO_CONFIG_EM) & (CO_CONFIG_EM_PRODUCER | CO_CONFIG_EM_HISTORY) */
|
||||
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_EM_PRODUCER) != 0) || defined CO_DOXYGEN
|
||||
bool_t producerEnabled; /**< True, if emergency producer is enabled, from Object dictionary */
|
||||
uint8_t nodeId; /**< Copy of CANopen node ID, from CO_EM_init() */
|
||||
CO_CANtx_t* CANtxBuff; /**< CAN transmit buffer */
|
||||
OD_extension_t OD_1014_extension; /**< Extension for OD object */
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_EM_PROD_CONFIGURABLE) != 0) || defined CO_DOXYGEN
|
||||
uint16_t producerCanId; /**< COB ID of emergency message, from Object dictionary */
|
||||
uint16_t CANdevTxIdx; /**< From CO_EM_init() */
|
||||
#endif
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_EM_PROD_INHIBIT) != 0) || defined CO_DOXYGEN
|
||||
uint32_t inhibitEmTime_us; /**< Inhibit time for emergency message, from Object dictionary */
|
||||
uint32_t inhibitEmTimer; /**< Internal timer for inhibit time */
|
||||
OD_extension_t OD_1015_extension; /**< Extension for OD object */
|
||||
#endif
|
||||
#endif /* (CO_CONFIG_EM) & CO_CONFIG_EM_PRODUCER */
|
||||
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_EM_HISTORY) != 0) || defined CO_DOXYGEN
|
||||
OD_extension_t OD_1003_extension; /**< Extension for OD object */
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_EM_STATUS_BITS) != 0) || defined CO_DOXYGEN
|
||||
OD_extension_t OD_statusBits_extension; /**< Extension for OD object */
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_EM_CONSUMER) != 0) || defined CO_DOXYGEN
|
||||
void (*pFunctSignalRx)(const uint16_t ident, const uint16_t errorCode, const uint8_t errorRegister,
|
||||
const uint8_t errorBit, const uint32_t infoCode); /**< From CO_EM_initCallbackRx() or NULL */
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
void (*pFunctSignalPre)(void* object); /**< From CO_EM_initCallbackPre() or NULL */
|
||||
void* functSignalObjectPre; /**< From CO_EM_initCallbackPre() or NULL */
|
||||
#endif
|
||||
} CO_EM_t;
|
||||
|
||||
/**
|
||||
* Initialize Emergency object.
|
||||
*
|
||||
* Function must be called in the communication reset section.
|
||||
*
|
||||
* @param em This object will be initialized.
|
||||
* @param fifo Fifo buffer for emergency producer and error history. It must be defined externally. Its size must be
|
||||
* capacity+1. See also @ref CO_EM_t, fifo.
|
||||
* @param fifoSize Size of the above fifo buffer. It is usually equal to the length of the OD array 0x1003 + 1. If
|
||||
* fifoSize is smaller than 2, then emergency producer and error history will not work and 'fifo' may be NULL.
|
||||
* @param CANdevTx CAN device for Emergency transmission.
|
||||
* @param OD_1001_errReg OD entry for 0x1001 - "Error register", entry is required, without IO extension.
|
||||
* @param OD_1014_cobIdEm OD entry for 0x1014 - "COB-ID EMCY", entry is required, IO extension is required.
|
||||
* @param CANdevTxIdx Index of transmit buffer in the above CAN device.
|
||||
* @param OD_1015_InhTime OD entry for 0x1015 - "Inhibit time EMCY", entry is optional (can be NULL), IO extension is
|
||||
* optional for runtime configuration.
|
||||
* @param OD_1003_preDefErr OD entry for 0x1003 - "Pre-defined error field". Emergency object has own memory buffer for
|
||||
* this entry. Entry is optional, IO extension is required.
|
||||
* @param OD_statusBits Custom OD entry for accessing errorStatusBits from
|
||||
* @ref CO_EM_t. Entry must have variable of size (CO_CONFIG_EM_ERR_STATUS_BITS_COUNT/8) bytes available for read/write
|
||||
* access on subindex 0. Emergency object has own memory buffer for this entry. Entry is optional, IO extension is
|
||||
* required.
|
||||
* @param CANdevRx CAN device for Emergency consumer reception.
|
||||
* @param CANdevRxIdx Index of receive buffer in the above CAN device.
|
||||
* @param nodeId CANopen node ID of this device (for default emergency producer)
|
||||
* @param [out] errInfo Additional information in case of error, may be NULL.
|
||||
*
|
||||
* @return @ref CO_ReturnError_t CO_ERROR_NO in case of success.
|
||||
*/
|
||||
CO_ReturnError_t CO_EM_init(CO_EM_t* em, CO_CANmodule_t* CANdevTx, const OD_entry_t* OD_1001_errReg,
|
||||
#if (((CO_CONFIG_EM) & (CO_CONFIG_EM_PRODUCER | CO_CONFIG_EM_HISTORY)) != 0) || defined CO_DOXYGEN
|
||||
CO_EM_fifo_t* fifo, uint8_t fifoSize,
|
||||
#endif
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_EM_PRODUCER) != 0) || defined CO_DOXYGEN
|
||||
OD_entry_t* OD_1014_cobIdEm, uint16_t CANdevTxIdx,
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_EM_PROD_INHIBIT) != 0) || defined CO_DOXYGEN
|
||||
OD_entry_t* OD_1015_InhTime,
|
||||
#endif
|
||||
#endif
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_EM_HISTORY) != 0) || defined CO_DOXYGEN
|
||||
OD_entry_t* OD_1003_preDefErr,
|
||||
#endif
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_EM_STATUS_BITS) != 0) || defined CO_DOXYGEN
|
||||
OD_entry_t* OD_statusBits,
|
||||
#endif
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_EM_CONSUMER) != 0) || defined CO_DOXYGEN
|
||||
CO_CANmodule_t* CANdevRx, uint16_t CANdevRxIdx,
|
||||
#endif
|
||||
const uint8_t nodeId, uint32_t* errInfo);
|
||||
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Initialize Emergency callback function.
|
||||
*
|
||||
* Function initializes optional callback function, which should immediately start processing of CO_EM_process()
|
||||
* function. Callback is called from CO_errorReport() or CO_errorReset() function. Those functions are fast and may be
|
||||
* called from any thread. Callback should immediately start mainline thread, which calls CO_EM_process() function.
|
||||
*
|
||||
* @param em This object.
|
||||
* @param object Pointer to object, which will be passed to pFunctSignal(). Can be NULL
|
||||
* @param pFunctSignal Pointer to the callback function. Not called if NULL.
|
||||
*/
|
||||
void CO_EM_initCallbackPre(CO_EM_t* em, void* object, void (*pFunctSignal)(void* object));
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_EM)&CO_CONFIG_EM_CONSUMER) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Initialize Emergency received callback function.
|
||||
*
|
||||
* Function initializes optional callback function, which executes after error condition is received.
|
||||
*
|
||||
* _ident_ argument from callback contains CAN-ID of the emergency message. If _ident_ == 0, then emergency message was
|
||||
* sent from this device.
|
||||
*
|
||||
* @remark Depending on the CAN driver implementation, this function is called inside an ISR or inside a mainline. Must
|
||||
* be thread safe.
|
||||
*
|
||||
* @param em This object.
|
||||
* @param pFunctSignalRx Pointer to the callback function. Not called if NULL.
|
||||
*/
|
||||
void CO_EM_initCallbackRx(CO_EM_t* em, void (*pFunctSignalRx)(const uint16_t ident, const uint16_t errorCode,
|
||||
const uint8_t errorRegister, const uint8_t errorBit,
|
||||
const uint32_t infoCode));
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Process Error control and Emergency object.
|
||||
*
|
||||
* Function must be called cyclically. It verifies some communication errors, calculates OD object 0x1001 - "Error
|
||||
* register" and sends emergency message if necessary.
|
||||
*
|
||||
* @param em This object.
|
||||
* @param NMTisPreOrOperational True if this node is NMT_PRE_OPERATIONAL or NMT_OPERATIONAL state.
|
||||
* @param timeDifference_us Time difference from previous function call in [microseconds].
|
||||
* @param [out] timerNext_us info to OS - see CO_process().
|
||||
*/
|
||||
void CO_EM_process(CO_EM_t* em, bool_t NMTisPreOrOperational, uint32_t timeDifference_us, uint32_t* timerNext_us);
|
||||
|
||||
/**
|
||||
* Set or reset error condition.
|
||||
*
|
||||
* Function can be called on any error condition inside CANopen stack or application. Function first checks change of
|
||||
* error condition (setError is true and error bit wasn't set or setError is false and error bit was set before). If
|
||||
* changed, then Emergency message is prepared and record in history is added. Emergency message is later sent by
|
||||
* CO_EM_process() function.
|
||||
*
|
||||
* Function is short and thread safe.
|
||||
*
|
||||
* @param em Emergency object.
|
||||
* @param setError True if error occurred or false if error resolved.
|
||||
* @param errorBit from @ref CO_EM_errorStatusBits_t.
|
||||
* @param errorCode from @ref CO_EM_errorCode_t.
|
||||
* @param infoCode 32 bit value is passed to bytes 4...7 of the Emergency message. It contains optional additional
|
||||
* information.
|
||||
*/
|
||||
void CO_error(CO_EM_t* em, bool_t setError, const uint8_t errorBit, uint16_t errorCode, uint32_t infoCode);
|
||||
|
||||
/**
|
||||
* Report error condition, for description of parameters see @ref CO_error.
|
||||
*/
|
||||
#define CO_errorReport(em, errorBit, errorCode, infoCode) CO_error(em, true, errorBit, errorCode, infoCode)
|
||||
|
||||
/**
|
||||
* Reset error condition, for description of parameters see @ref CO_error.
|
||||
*/
|
||||
#define CO_errorReset(em, errorBit, infoCode) CO_error(em, false, errorBit, CO_EMC_NO_ERROR, infoCode)
|
||||
|
||||
/**
|
||||
* Check specific error condition.
|
||||
*
|
||||
* Function returns true, if specific internal error is present.
|
||||
*
|
||||
* @param em Emergency object.
|
||||
* @param errorBit from @ref CO_EM_errorStatusBits_t.
|
||||
*
|
||||
* @return true if Error is present.
|
||||
*/
|
||||
static inline bool_t
|
||||
CO_isError(CO_EM_t* em, const uint8_t errorBit) {
|
||||
uint8_t index = errorBit >> 3;
|
||||
uint8_t bitmask = 1 << (errorBit & 0x7);
|
||||
|
||||
return (em == NULL || index >= (CO_CONFIG_EM_ERR_STATUS_BITS_COUNT / 8U)
|
||||
|| (em->errorStatusBits[index] & bitmask) != 0)
|
||||
? true
|
||||
: false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error register
|
||||
*
|
||||
* @param em Emergency object.
|
||||
*
|
||||
* @return Error register or 0 if doesn't exist.
|
||||
*/
|
||||
static inline uint8_t
|
||||
CO_getErrorRegister(CO_EM_t* em) {
|
||||
return (em == NULL || em->errorRegister == NULL) ? 0 : *em->errorRegister;
|
||||
}
|
||||
|
||||
/** @} */ /* CO_Emergency */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CO_EMERGENCY_H */
|
||||
488
Middleware/CANopenNode/301/CO_HBconsumer.c
Normal file
488
Middleware/CANopenNode/301/CO_HBconsumer.c
Normal file
@@ -0,0 +1,488 @@
|
||||
/*
|
||||
* CANopen Heartbeat consumer object.
|
||||
*
|
||||
* @file CO_HBconsumer.c
|
||||
* @ingroup CO_HBconsumer
|
||||
* @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 "301/CO_HBconsumer.h"
|
||||
|
||||
#if ((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_ENABLE) != 0
|
||||
|
||||
/* Verify HB consumer configuration */
|
||||
#if (((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_CHANGE) != 0) \
|
||||
&& (((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_MULTI) != 0)
|
||||
#error CO_CONFIG_HB_CONS_CALLBACK_CHANGE and CO_CONFIG_HB_CONS_CALLBACK_MULTI cannot be set simultaneously!
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
static void
|
||||
CO_HBcons_receive(void* object, void* msg) {
|
||||
CO_HBconsNode_t* HBconsNode = object;
|
||||
uint8_t DLC = CO_CANrxMsg_readDLC(msg);
|
||||
const uint8_t* data = CO_CANrxMsg_readData(msg);
|
||||
|
||||
if (DLC == 1U) {
|
||||
/* copy data and set 'new message' flag. */
|
||||
HBconsNode->NMTstate = (CO_NMT_internalState_t)data[0];
|
||||
CO_FLAG_SET(HBconsNode->CANrxNew);
|
||||
#if ((CO_CONFIG_HB_CONS)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0
|
||||
/* Optional signal to RTOS, which can resume task, which handles HBcons. */
|
||||
if (HBconsNode->pFunctSignalPre != NULL) {
|
||||
HBconsNode->pFunctSignalPre(HBconsNode->functSignalObjectPre);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize one Heartbeat consumer entry
|
||||
*
|
||||
* This function is called from the @ref CO_HBconsumer_init() or when writing to OD entry 1016.
|
||||
*
|
||||
* @param HBcons This object.
|
||||
* @param idx index of the node in HBcons object
|
||||
* @param nodeId see OD 0x1016 description
|
||||
* @param consumerTime_ms in milliseconds. see OD 0x1016 description
|
||||
* @return
|
||||
*/
|
||||
static CO_ReturnError_t CO_HBconsumer_initEntry(CO_HBconsumer_t* HBcons, uint8_t idx, uint8_t nodeId,
|
||||
uint16_t consumerTime_ms);
|
||||
|
||||
#if ((CO_CONFIG_HB_CONS)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0
|
||||
/*
|
||||
* Custom function for writing OD object "Consumer heartbeat time"
|
||||
*
|
||||
* For more information see file CO_ODinterface.h, OD_IO_t.
|
||||
*/
|
||||
static ODR_t
|
||||
OD_write_1016(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
|
||||
CO_HBconsumer_t* HBcons;
|
||||
|
||||
if (stream == NULL) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
HBcons = stream->object;
|
||||
|
||||
if ((buf == NULL) || (stream->subIndex < 1U)
|
||||
|| (stream->subIndex > HBcons->numberOfMonitoredNodes) || (count != sizeof(uint32_t))
|
||||
|| (countWritten == NULL)) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
uint32_t val = CO_getUint32(buf);
|
||||
uint8_t nodeId = (uint8_t)((val >> 16) & 0xFFU);
|
||||
uint16_t consumer_time = (uint16_t)(val & 0xFFFFU);
|
||||
CO_ReturnError_t ret = CO_HBconsumer_initEntry(HBcons, stream->subIndex - 1U, nodeId, consumer_time);
|
||||
if (ret != CO_ERROR_NO) {
|
||||
return ODR_PAR_INCOMPAT;
|
||||
}
|
||||
|
||||
/* write value to the original location in the Object Dictionary */
|
||||
return OD_writeOriginal(stream, buf, count, countWritten);
|
||||
}
|
||||
#endif
|
||||
|
||||
CO_ReturnError_t
|
||||
CO_HBconsumer_init(CO_HBconsumer_t* HBcons, CO_EM_t* em, CO_HBconsNode_t* monitoredNodes, uint8_t monitoredNodesCount,
|
||||
OD_entry_t* OD_1016_HBcons, CO_CANmodule_t* CANdevRx, uint16_t CANdevRxIdxStart, uint32_t* errInfo) {
|
||||
ODR_t odRet;
|
||||
|
||||
/* verify arguments */
|
||||
if ((HBcons == NULL) || (em == NULL) || (monitoredNodes == NULL) || (OD_1016_HBcons == NULL)
|
||||
|| (CANdevRx == NULL)) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* Configure object variables */
|
||||
(void)memset(HBcons, 0, sizeof(CO_HBconsumer_t));
|
||||
HBcons->em = em;
|
||||
HBcons->monitoredNodes = monitoredNodes;
|
||||
HBcons->CANdevRx = CANdevRx;
|
||||
HBcons->CANdevRxIdxStart = CANdevRxIdxStart;
|
||||
|
||||
/* get actual number of monitored nodes */
|
||||
HBcons->numberOfMonitoredNodes = ((OD_1016_HBcons->subEntriesCount - 1U) < monitoredNodesCount)
|
||||
? (OD_1016_HBcons->subEntriesCount - 1U)
|
||||
: monitoredNodesCount;
|
||||
|
||||
for (uint8_t i = 0; i < HBcons->numberOfMonitoredNodes; i++) {
|
||||
uint32_t val;
|
||||
odRet = OD_get_u32(OD_1016_HBcons, i + 1U, &val, true);
|
||||
if (odRet != ODR_OK) {
|
||||
if (errInfo != NULL) {
|
||||
*errInfo = OD_getIndex(OD_1016_HBcons);
|
||||
}
|
||||
return CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
|
||||
uint8_t nodeId = (uint8_t)((val >> 16) & 0xFFU);
|
||||
uint16_t consumer_time = (uint16_t)(val & 0xFFFFU);
|
||||
CO_ReturnError_t ret = CO_HBconsumer_initEntry(HBcons, i, nodeId, consumer_time);
|
||||
if (ret != CO_ERROR_NO) {
|
||||
if (errInfo != NULL) {
|
||||
*errInfo = OD_getIndex(OD_1016_HBcons);
|
||||
}
|
||||
/* don't break a program, if only value of a parameter is wrong */
|
||||
if (ret != CO_ERROR_OD_PARAMETERS) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* configure extension for OD */
|
||||
#if ((CO_CONFIG_HB_CONS)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0
|
||||
HBcons->OD_1016_extension.object = HBcons;
|
||||
HBcons->OD_1016_extension.read = OD_readOriginal;
|
||||
HBcons->OD_1016_extension.write = OD_write_1016;
|
||||
odRet = OD_extension_init(OD_1016_HBcons, &HBcons->OD_1016_extension);
|
||||
if (odRet != ODR_OK) {
|
||||
if (errInfo != NULL) {
|
||||
*errInfo = OD_getIndex(OD_1016_HBcons);
|
||||
}
|
||||
return CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
#endif
|
||||
|
||||
return CO_ERROR_NO;
|
||||
}
|
||||
|
||||
static CO_ReturnError_t
|
||||
CO_HBconsumer_initEntry(CO_HBconsumer_t* HBcons, uint8_t idx, uint8_t nodeId, uint16_t consumerTime_ms) {
|
||||
CO_ReturnError_t ret = CO_ERROR_NO;
|
||||
|
||||
/* verify arguments */
|
||||
if ((HBcons == NULL) || (idx >= HBcons->numberOfMonitoredNodes)) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* verify for duplicate entries */
|
||||
if ((consumerTime_ms != 0U) && (nodeId != 0U)) {
|
||||
for (uint8_t i = 0; i < HBcons->numberOfMonitoredNodes; i++) {
|
||||
CO_HBconsNode_t node = HBcons->monitoredNodes[i];
|
||||
if ((idx != i) && (node.time_us != 0U) && (node.nodeId == nodeId)) {
|
||||
ret = CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Configure one monitored node */
|
||||
if (ret == CO_ERROR_NO) {
|
||||
uint16_t COB_ID;
|
||||
|
||||
CO_HBconsNode_t* monitoredNode = &HBcons->monitoredNodes[idx];
|
||||
monitoredNode->nodeId = nodeId;
|
||||
monitoredNode->time_us = (uint32_t)consumerTime_ms * 1000U;
|
||||
monitoredNode->NMTstate = CO_NMT_UNKNOWN;
|
||||
#if (((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_CHANGE) != 0) \
|
||||
|| (((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_MULTI) != 0)
|
||||
monitoredNode->NMTstatePrev = CO_NMT_UNKNOWN;
|
||||
#endif
|
||||
CO_FLAG_CLEAR(monitoredNode->CANrxNew);
|
||||
|
||||
/* is channel used */
|
||||
if ((monitoredNode->nodeId != 0U) && (monitoredNode->time_us != 0U)) {
|
||||
COB_ID = monitoredNode->nodeId + (uint16_t)CO_CAN_ID_HEARTBEAT;
|
||||
monitoredNode->HBstate = CO_HBconsumer_UNKNOWN;
|
||||
} else {
|
||||
COB_ID = 0;
|
||||
monitoredNode->time_us = 0;
|
||||
monitoredNode->HBstate = CO_HBconsumer_UNCONFIGURED;
|
||||
}
|
||||
|
||||
/* configure Heartbeat consumer (or disable) CAN reception */
|
||||
ret = CO_CANrxBufferInit(HBcons->CANdevRx, HBcons->CANdevRxIdxStart + idx, COB_ID, 0x7FF, false,
|
||||
(void*)&HBcons->monitoredNodes[idx], CO_HBcons_receive);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_HB_CONS)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0
|
||||
void
|
||||
CO_HBconsumer_initCallbackPre(CO_HBconsumer_t* HBcons, void* object, void (*pFunctSignal)(void* object)) {
|
||||
if (HBcons != NULL) {
|
||||
uint8_t i;
|
||||
for (i = 0; i < HBcons->numberOfMonitoredNodes; i++) {
|
||||
HBcons->monitoredNodes[i].pFunctSignalPre = pFunctSignal;
|
||||
HBcons->monitoredNodes[i].functSignalObjectPre = object;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_CHANGE) != 0
|
||||
void
|
||||
CO_HBconsumer_initCallbackNmtChanged(CO_HBconsumer_t* HBcons, uint8_t idx, void* object,
|
||||
void (*pFunctSignal)(uint8_t nodeId, uint8_t idx, CO_NMT_internalState_t NMTstate,
|
||||
void* object)) {
|
||||
(void)idx;
|
||||
if (HBcons == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
HBcons->pFunctSignalNmtChanged = pFunctSignal;
|
||||
HBcons->pFunctSignalObjectNmtChanged = object;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_MULTI) != 0
|
||||
void
|
||||
CO_HBconsumer_initCallbackNmtChanged(CO_HBconsumer_t* HBcons, uint8_t idx, void* object,
|
||||
void (*pFunctSignal)(uint8_t nodeId, uint8_t idx, CO_NMT_internalState_t NMTstate,
|
||||
void* object)) {
|
||||
if (HBcons == NULL || idx >= HBcons->numberOfMonitoredNodes) {
|
||||
return;
|
||||
}
|
||||
|
||||
CO_HBconsNode_t* const monitoredNode = &HBcons->monitoredNodes[idx];
|
||||
monitoredNode->pFunctSignalNmtChanged = pFunctSignal;
|
||||
monitoredNode->pFunctSignalObjectNmtChanged = object;
|
||||
}
|
||||
|
||||
void
|
||||
CO_HBconsumer_initCallbackHeartbeatStarted(CO_HBconsumer_t* HBcons, uint8_t idx, void* object,
|
||||
void (*pFunctSignal)(uint8_t nodeId, uint8_t idx, void* object)) {
|
||||
CO_HBconsNode_t* monitoredNode;
|
||||
|
||||
if (HBcons == NULL || idx >= HBcons->numberOfMonitoredNodes) {
|
||||
return;
|
||||
}
|
||||
|
||||
monitoredNode = &HBcons->monitoredNodes[idx];
|
||||
monitoredNode->pFunctSignalHbStarted = pFunctSignal;
|
||||
monitoredNode->functSignalObjectHbStarted = object;
|
||||
}
|
||||
|
||||
void
|
||||
CO_HBconsumer_initCallbackTimeout(CO_HBconsumer_t* HBcons, uint8_t idx, void* object,
|
||||
void (*pFunctSignal)(uint8_t nodeId, uint8_t idx, void* object)) {
|
||||
CO_HBconsNode_t* monitoredNode;
|
||||
|
||||
if (HBcons == NULL || idx >= HBcons->numberOfMonitoredNodes) {
|
||||
return;
|
||||
}
|
||||
|
||||
monitoredNode = &HBcons->monitoredNodes[idx];
|
||||
monitoredNode->pFunctSignalTimeout = pFunctSignal;
|
||||
monitoredNode->functSignalObjectTimeout = object;
|
||||
}
|
||||
|
||||
void
|
||||
CO_HBconsumer_initCallbackRemoteReset(CO_HBconsumer_t* HBcons, uint8_t idx, void* object,
|
||||
void (*pFunctSignal)(uint8_t nodeId, uint8_t idx, void* object)) {
|
||||
CO_HBconsNode_t* monitoredNode;
|
||||
|
||||
if (HBcons == NULL || idx >= HBcons->numberOfMonitoredNodes) {
|
||||
return;
|
||||
}
|
||||
|
||||
monitoredNode = &HBcons->monitoredNodes[idx];
|
||||
monitoredNode->pFunctSignalRemoteReset = pFunctSignal;
|
||||
monitoredNode->functSignalObjectRemoteReset = object;
|
||||
}
|
||||
#endif /* (CO_CONFIG_HB_CONS) & CO_CONFIG_HB_CONS_CALLBACK_MULTI */
|
||||
|
||||
void
|
||||
CO_HBconsumer_process(CO_HBconsumer_t* HBcons, bool_t NMTisPreOrOperational, uint32_t timeDifference_us,
|
||||
uint32_t* timerNext_us) {
|
||||
(void)timerNext_us; /* may be unused */
|
||||
|
||||
bool_t allMonitoredActiveCurrent = true;
|
||||
bool_t allMonitoredOperationalCurrent = true;
|
||||
|
||||
if (NMTisPreOrOperational && HBcons->NMTisPreOrOperationalPrev) {
|
||||
for (uint8_t i = 0; i < HBcons->numberOfMonitoredNodes; i++) {
|
||||
uint32_t timeDifference_us_copy = timeDifference_us;
|
||||
CO_HBconsNode_t* const monitoredNode = &HBcons->monitoredNodes[i];
|
||||
|
||||
if (monitoredNode->HBstate == CO_HBconsumer_UNCONFIGURED) {
|
||||
/* continue, if node is not monitored */
|
||||
continue;
|
||||
}
|
||||
/* Verify if received message is heartbeat or bootup */
|
||||
if (CO_FLAG_READ(monitoredNode->CANrxNew)) {
|
||||
if (monitoredNode->NMTstate == CO_NMT_INITIALIZING) {
|
||||
/* bootup message */
|
||||
#if ((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_MULTI) != 0
|
||||
if (monitoredNode->pFunctSignalRemoteReset != NULL) {
|
||||
monitoredNode->pFunctSignalRemoteReset(monitoredNode->nodeId, i,
|
||||
monitoredNode->functSignalObjectRemoteReset);
|
||||
}
|
||||
#endif
|
||||
if (monitoredNode->HBstate == CO_HBconsumer_ACTIVE) {
|
||||
CO_errorReport(HBcons->em, CO_EM_HB_CONSUMER_REMOTE_RESET, CO_EMC_HEARTBEAT, i);
|
||||
}
|
||||
monitoredNode->HBstate = CO_HBconsumer_UNKNOWN;
|
||||
|
||||
} else {
|
||||
/* heartbeat message */
|
||||
#if ((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_MULTI) != 0
|
||||
if (monitoredNode->HBstate != CO_HBconsumer_ACTIVE
|
||||
&& monitoredNode->pFunctSignalHbStarted != NULL) {
|
||||
monitoredNode->pFunctSignalHbStarted(monitoredNode->nodeId, i,
|
||||
monitoredNode->functSignalObjectHbStarted);
|
||||
}
|
||||
#endif
|
||||
monitoredNode->HBstate = CO_HBconsumer_ACTIVE;
|
||||
/* reset timer */
|
||||
monitoredNode->timeoutTimer = 0;
|
||||
timeDifference_us_copy = 0;
|
||||
}
|
||||
CO_FLAG_CLEAR(monitoredNode->CANrxNew);
|
||||
}
|
||||
|
||||
/* Verify timeout */
|
||||
if (monitoredNode->HBstate == CO_HBconsumer_ACTIVE) {
|
||||
monitoredNode->timeoutTimer += timeDifference_us_copy;
|
||||
|
||||
if (monitoredNode->timeoutTimer >= monitoredNode->time_us) {
|
||||
/* timeout expired */
|
||||
#if ((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_MULTI) != 0
|
||||
if (monitoredNode->pFunctSignalTimeout != NULL) {
|
||||
monitoredNode->pFunctSignalTimeout(monitoredNode->nodeId, i,
|
||||
monitoredNode->functSignalObjectTimeout);
|
||||
}
|
||||
#endif
|
||||
CO_errorReport(HBcons->em, CO_EM_HEARTBEAT_CONSUMER, CO_EMC_HEARTBEAT, i);
|
||||
monitoredNode->NMTstate = CO_NMT_UNKNOWN;
|
||||
monitoredNode->HBstate = CO_HBconsumer_TIMEOUT;
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_HB_CONS)&CO_CONFIG_FLAG_TIMERNEXT) != 0
|
||||
else if (timerNext_us != NULL) {
|
||||
/* Calculate timerNext_us for next timeout checking. */
|
||||
uint32_t diff = monitoredNode->time_us - monitoredNode->timeoutTimer;
|
||||
if (*timerNext_us > diff) {
|
||||
*timerNext_us = diff;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (monitoredNode->HBstate != CO_HBconsumer_ACTIVE) {
|
||||
allMonitoredActiveCurrent = false;
|
||||
}
|
||||
if (monitoredNode->NMTstate != CO_NMT_OPERATIONAL) {
|
||||
allMonitoredOperationalCurrent = false;
|
||||
}
|
||||
#if (((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_CHANGE) != 0) \
|
||||
|| (((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_MULTI) != 0)
|
||||
/* Verify, if NMT state of monitored node changed */
|
||||
if (monitoredNode->NMTstate != monitoredNode->NMTstatePrev) {
|
||||
#if ((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_CHANGE) != 0
|
||||
if (HBcons->pFunctSignalNmtChanged != NULL) {
|
||||
HBcons->pFunctSignalNmtChanged(monitoredNode->nodeId, i, monitoredNode->NMTstate,
|
||||
HBcons->pFunctSignalObjectNmtChanged);
|
||||
#else
|
||||
if (monitoredNode->pFunctSignalNmtChanged != NULL) {
|
||||
monitoredNode->pFunctSignalNmtChanged(monitoredNode->nodeId, i, monitoredNode->NMTstate,
|
||||
monitoredNode->pFunctSignalObjectNmtChanged);
|
||||
#endif
|
||||
}
|
||||
monitoredNode->NMTstatePrev = monitoredNode->NMTstate;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} else if (NMTisPreOrOperational || HBcons->NMTisPreOrOperationalPrev) {
|
||||
/* (pre)operational state changed, clear variables */
|
||||
for (uint8_t i = 0; i < HBcons->numberOfMonitoredNodes; i++) {
|
||||
CO_HBconsNode_t* const monitoredNode = &HBcons->monitoredNodes[i];
|
||||
monitoredNode->NMTstate = CO_NMT_UNKNOWN;
|
||||
#if (((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_CHANGE) != 0) \
|
||||
|| (((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_MULTI) != 0)
|
||||
monitoredNode->NMTstatePrev = CO_NMT_UNKNOWN;
|
||||
#endif
|
||||
CO_FLAG_CLEAR(monitoredNode->CANrxNew);
|
||||
if (monitoredNode->HBstate != CO_HBconsumer_UNCONFIGURED) {
|
||||
monitoredNode->HBstate = CO_HBconsumer_UNKNOWN;
|
||||
}
|
||||
}
|
||||
allMonitoredActiveCurrent = false;
|
||||
allMonitoredOperationalCurrent = false;
|
||||
} else { /* MISRA C 2004 14.10 */
|
||||
}
|
||||
|
||||
/* Clear emergencies when all monitored nodes becomes active.
|
||||
* We only have one emergency index for all monitored nodes! */
|
||||
if (!HBcons->allMonitoredActive && allMonitoredActiveCurrent) {
|
||||
CO_errorReset(HBcons->em, CO_EM_HEARTBEAT_CONSUMER, 0);
|
||||
CO_errorReset(HBcons->em, CO_EM_HB_CONSUMER_REMOTE_RESET, 0);
|
||||
}
|
||||
|
||||
HBcons->allMonitoredActive = allMonitoredActiveCurrent;
|
||||
HBcons->allMonitoredOperational = allMonitoredOperationalCurrent;
|
||||
HBcons->NMTisPreOrOperationalPrev = NMTisPreOrOperational;
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_QUERY_FUNCT) != 0
|
||||
int8_t
|
||||
CO_HBconsumer_getIdxByNodeId(CO_HBconsumer_t* HBcons, uint8_t nodeId) {
|
||||
uint8_t i;
|
||||
CO_HBconsNode_t* monitoredNode;
|
||||
|
||||
if (HBcons == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* linear search for the node */
|
||||
monitoredNode = &HBcons->monitoredNodes[0];
|
||||
for (i = 0; i < HBcons->numberOfMonitoredNodes; i++) {
|
||||
if (monitoredNode->nodeId == nodeId) {
|
||||
return i;
|
||||
}
|
||||
monitoredNode++;
|
||||
}
|
||||
/* not found */
|
||||
return -1;
|
||||
}
|
||||
|
||||
CO_HBconsumer_state_t
|
||||
CO_HBconsumer_getState(CO_HBconsumer_t* HBcons, uint8_t idx) {
|
||||
CO_HBconsNode_t* monitoredNode;
|
||||
|
||||
if (HBcons == NULL || idx >= HBcons->numberOfMonitoredNodes) {
|
||||
return CO_HBconsumer_UNCONFIGURED;
|
||||
}
|
||||
|
||||
monitoredNode = &HBcons->monitoredNodes[idx];
|
||||
return monitoredNode->HBstate;
|
||||
}
|
||||
|
||||
int8_t
|
||||
CO_HBconsumer_getNmtState(CO_HBconsumer_t* HBcons, uint8_t idx, CO_NMT_internalState_t* nmtState) {
|
||||
CO_HBconsNode_t* monitoredNode;
|
||||
|
||||
if (HBcons == NULL || nmtState == NULL || idx >= HBcons->numberOfMonitoredNodes) {
|
||||
return -1;
|
||||
}
|
||||
*nmtState = CO_NMT_INITIALIZING;
|
||||
|
||||
monitoredNode = &HBcons->monitoredNodes[idx];
|
||||
|
||||
if (monitoredNode->HBstate == CO_HBconsumer_ACTIVE) {
|
||||
*nmtState = monitoredNode->NMTstate;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
#endif /* (CO_CONFIG_HB_CONS) & CO_CONFIG_HB_CONS_QUERY_FUNCT */
|
||||
|
||||
#endif /* (CO_CONFIG_HB_CONS) & CO_CONFIG_HB_CONS_ENABLE */
|
||||
284
Middleware/CANopenNode/301/CO_HBconsumer.h
Normal file
284
Middleware/CANopenNode/301/CO_HBconsumer.h
Normal file
@@ -0,0 +1,284 @@
|
||||
/**
|
||||
* CANopen Heartbeat consumer protocol.
|
||||
*
|
||||
* @file CO_HBconsumer.h
|
||||
* @ingroup CO_HBconsumer
|
||||
* @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.
|
||||
*/
|
||||
|
||||
#ifndef CO_HB_CONS_H
|
||||
#define CO_HB_CONS_H
|
||||
|
||||
#include "301/CO_driver.h"
|
||||
#include "301/CO_ODinterface.h"
|
||||
#include "301/CO_NMT_Heartbeat.h"
|
||||
#include "301/CO_Emergency.h"
|
||||
|
||||
/* default configuration, see CO_config.h */
|
||||
#ifndef CO_CONFIG_HB_CONS
|
||||
#define CO_CONFIG_HB_CONS \
|
||||
(CO_CONFIG_HB_CONS_ENABLE | CO_CONFIG_GLOBAL_FLAG_CALLBACK_PRE | CO_CONFIG_GLOBAL_FLAG_TIMERNEXT \
|
||||
| CO_CONFIG_GLOBAL_FLAG_OD_DYNAMIC)
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_HBconsumer Heartbeat consumer
|
||||
* CANopen Heartbeat consumer protocol.
|
||||
*
|
||||
* @ingroup CO_CANopen_301
|
||||
* @{
|
||||
* Heartbeat consumer monitors Heartbeat messages from remote nodes. If any monitored node don't send his Heartbeat in
|
||||
* specified time, Heartbeat consumer sends emergency message. If all monitored nodes are operational, then variable
|
||||
* _allMonitoredOperational_ inside CO_HBconsumer_t is set to true. Monitoring starts after the reception of the first
|
||||
* HeartBeat (not bootup).
|
||||
*
|
||||
* Heartbeat set up is done by writing to the OD registers 0x1016. To setup heartbeat consumer by application, use
|
||||
* @code ODR_t odRet = OD_set_u32(entry, subIndex, val, false); @endcode
|
||||
*
|
||||
* @see @ref CO_NMT_Heartbeat
|
||||
*/
|
||||
|
||||
/**
|
||||
* Heartbeat state of a node
|
||||
*/
|
||||
typedef enum {
|
||||
CO_HBconsumer_UNCONFIGURED = 0x00U, /**< Consumer entry inactive */
|
||||
CO_HBconsumer_UNKNOWN = 0x01U, /**< Consumer enabled, but no heartbeat received yet */
|
||||
CO_HBconsumer_ACTIVE = 0x02U, /**< Heartbeat received within set time */
|
||||
CO_HBconsumer_TIMEOUT = 0x03U, /**< No heatbeat received for set time */
|
||||
} CO_HBconsumer_state_t;
|
||||
|
||||
/**
|
||||
* One monitored node inside CO_HBconsumer_t.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t nodeId; /**< Node Id of the monitored node */
|
||||
CO_NMT_internalState_t NMTstate; /**< NMT state of the remote node (Heartbeat payload) */
|
||||
CO_HBconsumer_state_t HBstate; /**< Current heartbeat monitoring state of the remote node */
|
||||
uint32_t timeoutTimer; /**< Time since last heartbeat received */
|
||||
uint32_t time_us; /**< Consumer heartbeat time from OD */
|
||||
volatile void* CANrxNew; /**< Indication if new Heartbeat message received from the CAN bus */
|
||||
#if (((CO_CONFIG_HB_CONS)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
void (*pFunctSignalPre)(void* object); /**< From CO_HBconsumer_initCallbackPre() or NULL */
|
||||
void* functSignalObjectPre; /**< From CO_HBconsumer_initCallbackPre() or NULL */
|
||||
#endif
|
||||
#if (((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_CHANGE) != 0) \
|
||||
|| (((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_MULTI) != 0) || defined CO_DOXYGEN
|
||||
CO_NMT_internalState_t NMTstatePrev; /**< Previous value of the remote node (Heartbeat payload) */
|
||||
#endif
|
||||
#if (((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_MULTI) != 0) || defined CO_DOXYGEN
|
||||
/** Callback for remote NMT changed event. From CO_HBconsumer_initCallbackNmtChanged() or NULL. */
|
||||
void (*pFunctSignalNmtChanged)(uint8_t nodeId, uint8_t idx, CO_NMT_internalState_t NMTstate, void* object);
|
||||
void* pFunctSignalObjectNmtChanged; /**< Pointer to object */
|
||||
/** Callback for heartbeat state change to active event. From CO_HBconsumer_initCallbackHeartbeatStarted() or NULL.
|
||||
*/
|
||||
void (*pFunctSignalHbStarted)(uint8_t nodeId, uint8_t idx, void* object);
|
||||
void* functSignalObjectHbStarted; /**< Pointer to object */
|
||||
/** Callback for consumer timeout event. From CO_HBconsumer_initCallbackTimeout() or NULL. */
|
||||
void (*pFunctSignalTimeout)(uint8_t nodeId, uint8_t idx, void* object);
|
||||
void* functSignalObjectTimeout; /**< Pointer to object */
|
||||
/** Callback for remote reset event. From CO_HBconsumer_initCallbackRemoteReset() or NULL. */
|
||||
void (*pFunctSignalRemoteReset)(uint8_t nodeId, uint8_t idx, void* object);
|
||||
void* functSignalObjectRemoteReset; /**< Pointer to object */
|
||||
#endif
|
||||
} CO_HBconsNode_t;
|
||||
|
||||
/**
|
||||
* Heartbeat consumer object.
|
||||
*
|
||||
* Object is initilaized by CO_HBconsumer_init(). It contains an array of CO_HBconsNode_t objects.
|
||||
*/
|
||||
typedef struct {
|
||||
CO_EM_t* em; /**< From CO_HBconsumer_init() */
|
||||
CO_HBconsNode_t* monitoredNodes; /**< Array of monitored nodes, from CO_HBconsumer_init() */
|
||||
uint8_t numberOfMonitoredNodes; /**< Actual number of monitored nodes, size-of-the-above-array or
|
||||
number-of-array-elements-in-OD-0x1016, whichever is smaller. */
|
||||
bool_t allMonitoredActive; /**< True, if all monitored nodes are active or no node is monitored. Can be read
|
||||
by the application */
|
||||
bool_t allMonitoredOperational; /**< True, if all monitored nodes are NMT operational or no node is monitored. Can
|
||||
be read by the application */
|
||||
bool_t NMTisPreOrOperationalPrev; /**< previous state of the variable */
|
||||
CO_CANmodule_t* CANdevRx; /**< From CO_HBconsumer_init() */
|
||||
uint16_t CANdevRxIdxStart; /**< From CO_HBconsumer_init() */
|
||||
#if (((CO_CONFIG_HB_CONS)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0) || defined CO_DOXYGEN
|
||||
OD_extension_t OD_1016_extension; /**< Extension for OD object */
|
||||
#endif
|
||||
#if (((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_CHANGE) != 0) || defined CO_DOXYGEN
|
||||
/** Callback for remote NMT changed event. From CO_HBconsumer_initCallbackNmtChanged() or NULL. */
|
||||
void (*pFunctSignalNmtChanged)(uint8_t nodeId, uint8_t idx, CO_NMT_internalState_t NMTstate, void* object);
|
||||
void* pFunctSignalObjectNmtChanged; /**< Pointer to object */
|
||||
#endif
|
||||
} CO_HBconsumer_t;
|
||||
|
||||
/**
|
||||
* Initialize Heartbeat consumer object.
|
||||
*
|
||||
* Function must be called in the communication reset section.
|
||||
*
|
||||
* @param HBcons This object will be initialized.
|
||||
* @param em Emergency object.
|
||||
* @param monitoredNodes Array of monitored nodes, must be defined externally.
|
||||
* @param monitoredNodesCount Size of the above array, usually equal to number of array elements in OD 0x1016, valid
|
||||
* values are 1 to 127
|
||||
* @param OD_1016_HBcons OD entry for 0x1016 - "Consumer heartbeat time", entry is required, IO extension will be
|
||||
* applied.
|
||||
* @param CANdevRx CAN device for Heartbeat reception.
|
||||
* @param CANdevRxIdxStart Starting index of receive buffer in the above CAN device. Number of used indexes is equal to
|
||||
* monitoredNodesCount.
|
||||
* @param [out] errInfo Additional information in case of error, may be NULL.
|
||||
*
|
||||
* @return @ref CO_ReturnError_t CO_ERROR_NO in case of success.
|
||||
*/
|
||||
CO_ReturnError_t CO_HBconsumer_init(CO_HBconsumer_t* HBcons, CO_EM_t* em, CO_HBconsNode_t* monitoredNodes,
|
||||
uint8_t monitoredNodesCount, OD_entry_t* OD_1016_HBcons, CO_CANmodule_t* CANdevRx,
|
||||
uint16_t CANdevRxIdxStart, uint32_t* errInfo);
|
||||
|
||||
#if (((CO_CONFIG_HB_CONS)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Initialize Heartbeat consumer callback function.
|
||||
*
|
||||
* Function initializes optional callback function, which should immediately start processing of CO_HBconsumer_process()
|
||||
* function. Callback is called after HBconsumer message is received from the CAN bus.
|
||||
*
|
||||
* @param HBcons This object.
|
||||
* @param object Pointer to object, which will be passed to pFunctSignal(). Can be NULL
|
||||
* @param pFunctSignal Pointer to the callback function. Not called if NULL.
|
||||
*/
|
||||
void CO_HBconsumer_initCallbackPre(CO_HBconsumer_t* HBcons, void* object, void (*pFunctSignal)(void* object));
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_CHANGE) != 0) \
|
||||
|| (((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_MULTI) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Initialize Heartbeat consumer NMT changed callback function.
|
||||
*
|
||||
* Function initializes optional callback function, which is called when NMT state from the remote node changes.
|
||||
*
|
||||
* @param HBcons This object.
|
||||
* @param idx index of the node in HBcons object (only when CO_CONFIG_HB_CONS_CALLBACK_MULTI is enabled)
|
||||
* @param object Pointer to object, which will be passed to pFunctSignal(). Can be NULL.
|
||||
* @param pFunctSignal Pointer to the callback function. Not called if NULL.
|
||||
*/
|
||||
void CO_HBconsumer_initCallbackNmtChanged(CO_HBconsumer_t* HBcons, uint8_t idx, void* object,
|
||||
void (*pFunctSignal)(uint8_t nodeId, uint8_t idx,
|
||||
CO_NMT_internalState_t NMTstate, void* object));
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_CALLBACK_MULTI) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Initialize Heartbeat consumer started callback function.
|
||||
*
|
||||
* Function initializes optional callback function, which is called for the first received heartbeat after activating hb
|
||||
* consumer or timeout. Function may wake up external task, which handles this event.
|
||||
*
|
||||
* @param HBcons This object.
|
||||
* @param idx index of the node in HBcons object
|
||||
* @param object Pointer to object, which will be passed to pFunctSignal(). Can be NULL
|
||||
* @param pFunctSignal Pointer to the callback function. Not called if NULL.
|
||||
*/
|
||||
void CO_HBconsumer_initCallbackHeartbeatStarted(CO_HBconsumer_t* HBcons, uint8_t idx, void* object,
|
||||
void (*pFunctSignal)(uint8_t nodeId, uint8_t idx, void* object));
|
||||
|
||||
/**
|
||||
* Initialize Heartbeat consumer timeout callback function.
|
||||
*
|
||||
* Function initializes optional callback function, which is called when the node state changes from active to timeout.
|
||||
* Function may wake up external task, which handles this event.
|
||||
*
|
||||
* @param HBcons This object.
|
||||
* @param idx index of the node in HBcons object
|
||||
* @param object Pointer to object, which will be passed to pFunctSignal(). Can be NULL
|
||||
* @param pFunctSignal Pointer to the callback function. Not called if NULL.
|
||||
*/
|
||||
void CO_HBconsumer_initCallbackTimeout(CO_HBconsumer_t* HBcons, uint8_t idx, void* object,
|
||||
void (*pFunctSignal)(uint8_t nodeId, uint8_t idx, void* object));
|
||||
|
||||
/**
|
||||
* Initialize Heartbeat consumer remote reset detected callback function.
|
||||
*
|
||||
* Function initializes optional callback function, which is called when a bootup message is received from the remote
|
||||
* node. Function may wake up external task, which handles this event.
|
||||
*
|
||||
* @param HBcons This object.
|
||||
* @param idx index of the node in HBcons object
|
||||
* @param object Pointer to object, which will be passed to pFunctSignal(). Can be NULL
|
||||
* @param pFunctSignal Pointer to the callback function. Not called if NULL.
|
||||
*/
|
||||
void CO_HBconsumer_initCallbackRemoteReset(CO_HBconsumer_t* HBcons, uint8_t idx, void* object,
|
||||
void (*pFunctSignal)(uint8_t nodeId, uint8_t idx, void* object));
|
||||
#endif /* (CO_CONFIG_HB_CONS) & CO_CONFIG_HB_CONS_CALLBACK_MULTI */
|
||||
|
||||
/**
|
||||
* Process Heartbeat consumer object.
|
||||
*
|
||||
* Function must be called cyclically.
|
||||
*
|
||||
* @param HBcons This object.
|
||||
* @param NMTisPreOrOperational True if this node is NMT_PRE_OPERATIONAL or NMT_OPERATIONAL.
|
||||
* @param timeDifference_us Time difference from previous function call in [microseconds].
|
||||
* @param [out] timerNext_us info to OS - see CO_process().
|
||||
*/
|
||||
void CO_HBconsumer_process(CO_HBconsumer_t* HBcons, bool_t NMTisPreOrOperational, uint32_t timeDifference_us,
|
||||
uint32_t* timerNext_us);
|
||||
|
||||
#if (((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_QUERY_FUNCT) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Get the heartbeat producer object index by node ID
|
||||
*
|
||||
* @param HBcons This object.
|
||||
* @param nodeId producer node ID
|
||||
* @return index. -1 if not found
|
||||
*/
|
||||
int8_t CO_HBconsumer_getIdxByNodeId(CO_HBconsumer_t* HBcons, uint8_t nodeId);
|
||||
|
||||
/**
|
||||
* Get the current state of a heartbeat producer by the index in OD 0x1016
|
||||
*
|
||||
* @param HBcons This object.
|
||||
* @param idx object sub index
|
||||
* @return #CO_HBconsumer_state_t
|
||||
*/
|
||||
CO_HBconsumer_state_t CO_HBconsumer_getState(CO_HBconsumer_t* HBcons, uint8_t idx);
|
||||
|
||||
/**
|
||||
* Get the current NMT state of a heartbeat producer by the index in OD 0x1016
|
||||
*
|
||||
* NMT state is only available when heartbeat is enabled for this index!
|
||||
*
|
||||
* @param HBcons This object.
|
||||
* @param idx object sub index
|
||||
* @param [out] nmtState of this index
|
||||
* @retval 0 NMT state has been received and is valid
|
||||
* @retval -1 not valid
|
||||
*/
|
||||
int8_t CO_HBconsumer_getNmtState(CO_HBconsumer_t* HBcons, uint8_t idx, CO_NMT_internalState_t* nmtState);
|
||||
|
||||
#endif /* (CO_CONFIG_HB_CONS) & CO_CONFIG_HB_CONS_QUERY_FUNCT */
|
||||
|
||||
/** @} */ /* CO_HBconsumer */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_HB_CONS) & CO_CONFIG_HB_CONS_ENABLE */
|
||||
|
||||
#endif /* CO_HB_CONS_H */
|
||||
337
Middleware/CANopenNode/301/CO_NMT_Heartbeat.c
Normal file
337
Middleware/CANopenNode/301/CO_NMT_Heartbeat.c
Normal file
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
* CANopen NMT and Heartbeat producer object.
|
||||
*
|
||||
* @file CO_NMT_Heartbeat.c
|
||||
* @ingroup CO_NMT_Heartbeat
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2004 - 2020 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 "301/CO_NMT_Heartbeat.h"
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
static void
|
||||
CO_NMT_receive(void *object, void *msg)
|
||||
{
|
||||
uint8_t DLC = CO_CANrxMsg_readDLC(msg);
|
||||
const uint8_t *data = CO_CANrxMsg_readData(msg);
|
||||
CO_NMT_command_t command = (CO_NMT_command_t)data[0];
|
||||
uint8_t nodeId = data[1];
|
||||
|
||||
CO_NMT_t *NMT = (CO_NMT_t *)object;
|
||||
|
||||
if ((DLC == 2U) && ((nodeId == 0U) || (nodeId == NMT->nodeId)))
|
||||
{
|
||||
NMT->internalCommand = command;
|
||||
|
||||
#if ((CO_CONFIG_NMT) & CO_CONFIG_FLAG_CALLBACK_PRE) != 0
|
||||
/* Optional signal to RTOS, which can resume task, which handles NMT. */
|
||||
if (NMT->pFunctSignalPre != NULL)
|
||||
{
|
||||
NMT->pFunctSignalPre(NMT->functSignalObjectPre);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Custom function for writing OD object "Producer heartbeat time"
|
||||
*
|
||||
* For more information see file CO_ODinterface.h, OD_IO_t.
|
||||
*/
|
||||
static ODR_t
|
||||
OD_write_1017(OD_stream_t *stream, const void *buf, OD_size_t count, OD_size_t *countWritten)
|
||||
{
|
||||
if ((stream == NULL) || (stream->subIndex != 0U) || (buf == NULL) || (count != sizeof(uint16_t)) || (countWritten == NULL))
|
||||
{
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
CO_NMT_t *NMT = (CO_NMT_t *)stream->object;
|
||||
|
||||
/* update object, send Heartbeat immediately */
|
||||
NMT->HBproducerTime_us = (uint32_t)CO_getUint16(buf) * 1000U;
|
||||
NMT->HBproducerTimer = 0;
|
||||
|
||||
/* write value to the original location in the Object Dictionary */
|
||||
return OD_writeOriginal(stream, buf, count, countWritten);
|
||||
}
|
||||
|
||||
CO_ReturnError_t
|
||||
CO_NMT_init(CO_NMT_t *NMT, OD_entry_t *OD_1017_ProducerHbTime, CO_EM_t *em, uint8_t nodeId, uint16_t NMTcontrol,
|
||||
uint16_t firstHBTime_ms, CO_CANmodule_t *NMT_CANdevRx, uint16_t NMT_rxIdx, uint16_t CANidRxNMT,
|
||||
#if (((CO_CONFIG_NMT) & CO_CONFIG_NMT_MASTER) != 0) || defined CO_DOXYGEN
|
||||
CO_CANmodule_t *NMT_CANdevTx, uint16_t NMT_txIdx, uint16_t CANidTxNMT,
|
||||
#endif
|
||||
CO_CANmodule_t *HB_CANdevTx, uint16_t HB_txIdx, uint16_t CANidTxHB, uint32_t *errInfo)
|
||||
{
|
||||
CO_ReturnError_t ret = CO_ERROR_NO;
|
||||
|
||||
/* verify arguments */
|
||||
if ((NMT == NULL) || (OD_1017_ProducerHbTime == NULL) || (em == NULL) || (NMT_CANdevRx == NULL) || (HB_CANdevTx == NULL)
|
||||
#if ((CO_CONFIG_NMT) & CO_CONFIG_NMT_MASTER) != 0
|
||||
|| (NMT_CANdevTx == NULL)
|
||||
#endif
|
||||
)
|
||||
{
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* clear the object */
|
||||
(void)memset(NMT, 0, sizeof(CO_NMT_t));
|
||||
|
||||
/* Configure object variables */
|
||||
NMT->operatingState = CO_NMT_INITIALIZING;
|
||||
NMT->operatingStatePrev = CO_NMT_INITIALIZING;
|
||||
NMT->nodeId = nodeId;
|
||||
NMT->NMTcontrol = NMTcontrol;
|
||||
NMT->em = em;
|
||||
NMT->HBproducerTimer = (uint32_t)firstHBTime_ms * 1000U;
|
||||
|
||||
/* get and verify required "Producer heartbeat time" from Object Dict. */
|
||||
uint16_t HBprodTime_ms;
|
||||
ODR_t odRet = OD_get_u16(OD_1017_ProducerHbTime, 0, &HBprodTime_ms, true);
|
||||
if (odRet != ODR_OK)
|
||||
{
|
||||
if (errInfo != NULL)
|
||||
{
|
||||
*errInfo = OD_getIndex(OD_1017_ProducerHbTime);
|
||||
}
|
||||
return CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
NMT->HBproducerTime_us = (uint32_t)HBprodTime_ms * 1000U;
|
||||
|
||||
NMT->OD_1017_extension.object = NMT;
|
||||
NMT->OD_1017_extension.read = OD_readOriginal;
|
||||
NMT->OD_1017_extension.write = OD_write_1017;
|
||||
odRet = OD_extension_init(OD_1017_ProducerHbTime, &NMT->OD_1017_extension);
|
||||
if (odRet != ODR_OK)
|
||||
{
|
||||
if (errInfo != NULL)
|
||||
{
|
||||
*errInfo = OD_getIndex(OD_1017_ProducerHbTime);
|
||||
}
|
||||
return CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
|
||||
if (NMT->HBproducerTimer > NMT->HBproducerTime_us)
|
||||
{
|
||||
NMT->HBproducerTimer = NMT->HBproducerTime_us;
|
||||
}
|
||||
|
||||
/* configure NMT CAN reception */
|
||||
ret = CO_CANrxBufferInit(NMT_CANdevRx, NMT_rxIdx, CANidRxNMT, 0x7FF, false, (void *)NMT, CO_NMT_receive);
|
||||
if (ret != CO_ERROR_NO)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_NMT) & CO_CONFIG_NMT_MASTER) != 0
|
||||
/* configure NMT CAN transmission */
|
||||
NMT->NMT_CANdevTx = NMT_CANdevTx;
|
||||
NMT->NMT_TXbuff = CO_CANtxBufferInit(NMT_CANdevTx, NMT_txIdx, CANidTxNMT, false, 2, false);
|
||||
if (NMT->NMT_TXbuff == NULL)
|
||||
{
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* configure HB CAN transmission */
|
||||
NMT->HB_CANdevTx = HB_CANdevTx;
|
||||
NMT->HB_TXbuff = CO_CANtxBufferInit(HB_CANdevTx, HB_txIdx, CANidTxHB, false, 1, false);
|
||||
if (NMT->HB_TXbuff == NULL)
|
||||
{
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_NMT) & CO_CONFIG_FLAG_CALLBACK_PRE) != 0
|
||||
void CO_NMT_initCallbackPre(CO_NMT_t *NMT, void *object, void (*pFunctSignal)(void *object))
|
||||
{
|
||||
if (NMT != NULL)
|
||||
{
|
||||
NMT->pFunctSignalPre = pFunctSignal;
|
||||
NMT->functSignalObjectPre = object;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ((CO_CONFIG_NMT) & CO_CONFIG_NMT_CALLBACK_CHANGE) != 0
|
||||
void CO_NMT_initCallbackChanged(CO_NMT_t *NMT, void (*pFunctNMT)(CO_NMT_internalState_t state))
|
||||
{
|
||||
if (NMT != NULL)
|
||||
{
|
||||
NMT->pFunctNMT = pFunctNMT;
|
||||
if (NMT->pFunctNMT != NULL)
|
||||
{
|
||||
NMT->pFunctNMT(NMT->operatingState);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
CO_NMT_reset_cmd_t
|
||||
CO_NMT_process(CO_NMT_t *NMT, CO_NMT_internalState_t *NMTstate, uint32_t timeDifference_us, uint32_t *timerNext_us)
|
||||
{
|
||||
(void)timerNext_us; /* may be unused */
|
||||
CO_NMT_internalState_t NMTstateCpy = NMT->operatingState;
|
||||
CO_NMT_reset_cmd_t resetCommand = CO_RESET_NOT;
|
||||
bool_t NNTinit = NMTstateCpy == CO_NMT_INITIALIZING;
|
||||
|
||||
NMT->HBproducerTimer = (NMT->HBproducerTimer > timeDifference_us) ? (NMT->HBproducerTimer - timeDifference_us) : 0U;
|
||||
|
||||
/* Send heartbeat producer message if:
|
||||
* - First start, send bootup message or
|
||||
* - HB producer enabled and: Timer expired or NMT->operatingState changed */
|
||||
if (NNTinit || ((NMT->HBproducerTime_us != 0U) && ((NMT->HBproducerTimer == 0U) || (NMTstateCpy != NMT->operatingStatePrev))))
|
||||
{
|
||||
NMT->HB_TXbuff->data[0] = (uint8_t)NMTstateCpy;
|
||||
(void)CO_CANsend(NMT->HB_CANdevTx, NMT->HB_TXbuff);
|
||||
|
||||
if (NMTstateCpy == CO_NMT_INITIALIZING)
|
||||
{
|
||||
/* NMT slave self starting */
|
||||
NMTstateCpy = (((uint16_t)NMT->NMTcontrol & (uint16_t)CO_NMT_STARTUP_TO_OPERATIONAL) != 0U)
|
||||
? CO_NMT_OPERATIONAL
|
||||
: CO_NMT_PRE_OPERATIONAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Start timer from the beginning. If OS is slow, time sliding may occur. However,
|
||||
* heartbeat is not for synchronization, it is for health report. In case of
|
||||
* initializing, timer is set in the CO_NMT_init() function with pre-defined value. */
|
||||
NMT->HBproducerTimer = NMT->HBproducerTime_us;
|
||||
}
|
||||
}
|
||||
NMT->operatingStatePrev = NMTstateCpy;
|
||||
|
||||
/* process internal NMT commands, received from CO_NMT_receive() or CO_NMT_sendCommand() */
|
||||
if (NMT->internalCommand != CO_NMT_NO_COMMAND)
|
||||
{
|
||||
switch (NMT->internalCommand)
|
||||
{
|
||||
case CO_NMT_ENTER_OPERATIONAL:
|
||||
NMTstateCpy = CO_NMT_OPERATIONAL;
|
||||
break;
|
||||
case CO_NMT_ENTER_STOPPED:
|
||||
NMTstateCpy = CO_NMT_STOPPED;
|
||||
break;
|
||||
case CO_NMT_ENTER_PRE_OPERATIONAL:
|
||||
NMTstateCpy = CO_NMT_PRE_OPERATIONAL;
|
||||
break;
|
||||
case CO_NMT_RESET_NODE:
|
||||
resetCommand = CO_RESET_APP;
|
||||
break;
|
||||
case CO_NMT_RESET_COMMUNICATION:
|
||||
resetCommand = CO_RESET_COMM;
|
||||
break;
|
||||
case CO_NMT_NO_COMMAND:
|
||||
default:
|
||||
/* done */
|
||||
break;
|
||||
}
|
||||
NMT->internalCommand = CO_NMT_NO_COMMAND;
|
||||
}
|
||||
|
||||
/* verify NMT transitions based on error register */
|
||||
bool_t ErrOnBusOffHB = (((uint16_t)NMT->NMTcontrol & (uint16_t)CO_NMT_ERR_ON_BUSOFF_HB) != 0U);
|
||||
bool_t ErrBusOff = CO_isError(NMT->em, CO_EM_CAN_TX_BUS_OFF);
|
||||
bool_t ErrHbCons = CO_isError(NMT->em, CO_EM_HEARTBEAT_CONSUMER);
|
||||
bool_t ErrHbConsRemote = CO_isError(NMT->em, CO_EM_HB_CONSUMER_REMOTE_RESET);
|
||||
bool_t busOff_HB = ErrOnBusOffHB && (ErrBusOff || ErrHbCons || ErrHbConsRemote);
|
||||
|
||||
bool_t ErrNMTErrReg = (((uint16_t)NMT->NMTcontrol & (uint16_t)CO_NMT_ERR_ON_ERR_REG) != 0U);
|
||||
bool_t ErrNMTcontrol = ((CO_getErrorRegister(NMT->em) & (uint8_t)NMT->NMTcontrol) != 0U);
|
||||
bool_t errRegMasked = ErrNMTErrReg && ErrNMTcontrol;
|
||||
|
||||
if ((NMTstateCpy == CO_NMT_OPERATIONAL) && (busOff_HB || errRegMasked))
|
||||
{
|
||||
NMTstateCpy = (((uint16_t)NMT->NMTcontrol & (uint16_t)CO_NMT_ERR_TO_STOPPED) != 0U) ? CO_NMT_STOPPED
|
||||
: CO_NMT_PRE_OPERATIONAL;
|
||||
}
|
||||
else if ((((uint16_t)NMT->NMTcontrol & (uint16_t)CO_NMT_ERR_FREE_TO_OPERATIONAL) != 0U) && (NMTstateCpy == CO_NMT_PRE_OPERATIONAL) && (!busOff_HB && !errRegMasked))
|
||||
{
|
||||
NMTstateCpy = CO_NMT_OPERATIONAL;
|
||||
}
|
||||
else
|
||||
{ /* MISRA C 2004 14.10 */
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_NMT) & CO_CONFIG_NMT_CALLBACK_CHANGE) != 0
|
||||
/* Notify operating state change */
|
||||
if ((NMT->operatingStatePrev != NMTstateCpy) || NNTinit)
|
||||
{
|
||||
if (NMT->pFunctNMT != NULL)
|
||||
{
|
||||
NMT->pFunctNMT(NMTstateCpy);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ((CO_CONFIG_NMT) & CO_CONFIG_FLAG_TIMERNEXT) != 0
|
||||
/* Calculate, when next Heartbeat needs to be send */
|
||||
if ((NMT->HBproducerTime_us != 0U) && (timerNext_us != NULL))
|
||||
{
|
||||
if (NMT->operatingStatePrev != NMTstateCpy)
|
||||
{
|
||||
*timerNext_us = 0;
|
||||
}
|
||||
else if (*timerNext_us > NMT->HBproducerTimer)
|
||||
{
|
||||
*timerNext_us = NMT->HBproducerTimer;
|
||||
}
|
||||
else
|
||||
{ /* MISRA C 2004 14.10 */
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
NMT->operatingState = NMTstateCpy;
|
||||
if (NMTstate != NULL)
|
||||
{
|
||||
*NMTstate = NMTstateCpy;
|
||||
}
|
||||
|
||||
return resetCommand;
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_NMT) & CO_CONFIG_NMT_MASTER) != 0
|
||||
CO_ReturnError_t
|
||||
CO_NMT_sendCommand(CO_NMT_t *NMT, CO_NMT_command_t command, uint8_t nodeID)
|
||||
{
|
||||
/* verify arguments */
|
||||
if (NMT == NULL)
|
||||
{
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* Apply NMT command also to this node, if set so. */
|
||||
if ((nodeID == 0U) || (nodeID == NMT->nodeId))
|
||||
{
|
||||
NMT->internalCommand = command;
|
||||
}
|
||||
|
||||
/* Send NMT master message. */
|
||||
NMT->NMT_TXbuff->data[0] = (uint8_t)command;
|
||||
NMT->NMT_TXbuff->data[1] = nodeID;
|
||||
return CO_CANsend(NMT->NMT_CANdevTx, NMT->NMT_TXbuff);
|
||||
}
|
||||
#endif
|
||||
293
Middleware/CANopenNode/301/CO_NMT_Heartbeat.h
Normal file
293
Middleware/CANopenNode/301/CO_NMT_Heartbeat.h
Normal file
@@ -0,0 +1,293 @@
|
||||
/**
|
||||
* CANopen Network management and Heartbeat producer protocol.
|
||||
*
|
||||
* @file CO_NMT_Heartbeat.h
|
||||
* @ingroup CO_NMT_Heartbeat
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2004 - 2020 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.
|
||||
*/
|
||||
|
||||
#ifndef CO_NMT_HEARTBEAT_H
|
||||
#define CO_NMT_HEARTBEAT_H
|
||||
|
||||
#include "301/CO_driver.h"
|
||||
#include "301/CO_ODinterface.h"
|
||||
#include "301/CO_Emergency.h"
|
||||
|
||||
/* default configuration, see CO_config.h */
|
||||
#ifndef CO_CONFIG_NMT
|
||||
#define CO_CONFIG_NMT (CO_CONFIG_GLOBAL_FLAG_CALLBACK_PRE | CO_CONFIG_GLOBAL_FLAG_TIMERNEXT)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_NMT_Heartbeat NMT and Heartbeat
|
||||
* CANopen Network management and Heartbeat producer protocol.
|
||||
*
|
||||
* @ingroup CO_CANopen_301
|
||||
* @{
|
||||
* CANopen device can be in one of the @ref CO_NMT_internalState_t
|
||||
* - Initializing. It is active before CANopen is initialized.
|
||||
* - Pre-operational. All CANopen objects are active, except PDOs.
|
||||
* - Operational. Process data objects (PDOs) are active too.
|
||||
* - Stopped. Only Heartbeat producer and NMT consumer are active.
|
||||
*
|
||||
* NMT master can change the internal state of the devices by sending @ref CO_NMT_command_t.
|
||||
*
|
||||
* ### NMT message contents:
|
||||
*
|
||||
* Byte | Description
|
||||
* -----|-----------------------------------------------------------
|
||||
* 0 | @ref CO_NMT_command_t
|
||||
* 1 | Node ID. If zero, command addresses all nodes.
|
||||
*
|
||||
* ### Heartbeat message contents:
|
||||
*
|
||||
* Byte | Description
|
||||
* -----|-----------------------------------------------------------
|
||||
* 0 | @ref CO_NMT_internalState_t
|
||||
*
|
||||
* See @ref CO_Default_CAN_ID_t for CAN identifiers.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal network state of the CANopen node
|
||||
*/
|
||||
typedef enum {
|
||||
CO_NMT_UNKNOWN = -1, /**< -1, Device state is unknown (for heartbeat consumer) */
|
||||
CO_NMT_INITIALIZING = 0, /**< 0, Device is initializing */
|
||||
CO_NMT_PRE_OPERATIONAL = 127, /**< 127, Device is in pre-operational state */
|
||||
CO_NMT_OPERATIONAL = 5, /**< 5, Device is in operational state */
|
||||
CO_NMT_STOPPED = 4 /**< 4, Device is stopped */
|
||||
} CO_NMT_internalState_t;
|
||||
|
||||
/**
|
||||
* Commands from NMT master.
|
||||
*/
|
||||
typedef enum {
|
||||
CO_NMT_NO_COMMAND = 0, /**< 0, No command */
|
||||
CO_NMT_ENTER_OPERATIONAL = 1, /**< 1, Start device */
|
||||
CO_NMT_ENTER_STOPPED = 2, /**< 2, Stop device */
|
||||
CO_NMT_ENTER_PRE_OPERATIONAL = 128, /**< 128, Put device into pre-operational */
|
||||
CO_NMT_RESET_NODE = 129, /**< 129, Reset device */
|
||||
CO_NMT_RESET_COMMUNICATION = 130 /**< 130, Reset CANopen communication on device */
|
||||
} CO_NMT_command_t;
|
||||
|
||||
/**
|
||||
* Return code from CO_NMT_process() that tells application code what to reset.
|
||||
*/
|
||||
typedef enum {
|
||||
CO_RESET_NOT = 0, /**< 0, Normal return, no action */
|
||||
CO_RESET_COMM = 1, /**< 1, Application must provide communication reset. */
|
||||
CO_RESET_APP = 2, /**< 2, Application must provide complete device reset */
|
||||
CO_RESET_QUIT = 3 /**< 3, Application must quit, no reset of microcontroller (command is not requested by the
|
||||
stack.) */
|
||||
} CO_NMT_reset_cmd_t;
|
||||
|
||||
/**
|
||||
* @defgroup CO_NMT_control_t NMT control bitfield for NMT internal state.
|
||||
* @{
|
||||
*
|
||||
* Variable of this type is passed to @ref CO_NMT_init() function. It controls behavior of the @ref
|
||||
* CO_NMT_internalState_t of the device according to CANopen error register.
|
||||
*
|
||||
* Internal NMT state is controlled also with external NMT command, @ref CO_NMT_sendInternalCommand() or @ref
|
||||
* CO_NMT_sendCommand() functions.
|
||||
*/
|
||||
|
||||
/** First 8 bits can be used to specify bitmask for the @ref CO_errorRegister_t to get relevant bits for the
|
||||
* calculation. */
|
||||
#define CO_NMT_ERR_REG_MASK 0x00FFU
|
||||
/** If bit is set then device enters NMT operational state after the initialization phase otherwise it enters NMT
|
||||
* pre-operational state. */
|
||||
#define CO_NMT_STARTUP_TO_OPERATIONAL 0x0100U
|
||||
/** If bit is set and device is operational it enters NMT pre-operational or stopped state if CAN bus is off or
|
||||
* heartbeat consumer timeout is detected. */
|
||||
#define CO_NMT_ERR_ON_BUSOFF_HB 0x1000U
|
||||
/** If bit is set and device is operational it enters NMT pre-operational or stopped state if masked CANopen error
|
||||
* register is different than zero. */
|
||||
#define CO_NMT_ERR_ON_ERR_REG 0x2000U
|
||||
/** If bit is set and CO_NMT_ERR_ON_xx condition is met then device will enter NMT stopped state otherwise it will enter
|
||||
* NMT pre-op state. */
|
||||
#define CO_NMT_ERR_TO_STOPPED 0x4000U
|
||||
/** If bit is set and device is pre-operational it enters NMT operational state automatically if conditions from
|
||||
* CO_NMT_ERR_ON_xx are all false. */
|
||||
#define CO_NMT_ERR_FREE_TO_OPERATIONAL 0x8000U
|
||||
|
||||
/** @} */ /* CO_NMT_control_t */
|
||||
|
||||
/**
|
||||
* NMT consumer and Heartbeat producer object
|
||||
*/
|
||||
typedef struct {
|
||||
CO_NMT_internalState_t operatingState; /**< Current NMT operating state. */
|
||||
CO_NMT_internalState_t operatingStatePrev; /**< Previous NMT operating state. */
|
||||
CO_NMT_command_t internalCommand; /**< NMT internal command from CO_NMT_receive() or CO_NMT_sendCommand(), processed
|
||||
in CO_NMT_process(). */
|
||||
uint8_t nodeId; /**< From CO_NMT_init() */
|
||||
uint16_t NMTcontrol; /**< From CO_NMT_init() */
|
||||
uint32_t HBproducerTime_us; /**< Producer heartbeat time, calculated from OD 0x1017 */
|
||||
uint32_t HBproducerTimer; /**< Internal timer for HB producer */
|
||||
OD_extension_t OD_1017_extension; /**< Extension for OD object */
|
||||
CO_EM_t* em; /**< From CO_NMT_init() */
|
||||
#if (((CO_CONFIG_NMT)&CO_CONFIG_NMT_MASTER) != 0) || defined CO_DOXYGEN
|
||||
CO_CANmodule_t* NMT_CANdevTx; /**< From CO_NMT_init() */
|
||||
CO_CANtx_t* NMT_TXbuff; /**< CAN transmit buffer for NMT master message */
|
||||
#endif
|
||||
CO_CANmodule_t* HB_CANdevTx; /**< From CO_NMT_init() */
|
||||
CO_CANtx_t* HB_TXbuff; /**< CAN transmit buffer for heartbeat message */
|
||||
#if (((CO_CONFIG_NMT)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
void (*pFunctSignalPre)(void* object); /**< From CO_NMT_initCallbackPre() or NULL */
|
||||
void* functSignalObjectPre; /**< From CO_NMT_initCallbackPre() or NULL */
|
||||
#endif
|
||||
#if (((CO_CONFIG_NMT)&CO_CONFIG_NMT_CALLBACK_CHANGE) != 0) || defined CO_DOXYGEN
|
||||
void (*pFunctNMT)(CO_NMT_internalState_t state); /**< From CO_NMT_initCallbackChanged() or NULL */
|
||||
#endif
|
||||
} CO_NMT_t;
|
||||
|
||||
/**
|
||||
* Initialize NMT and Heartbeat producer object.
|
||||
*
|
||||
* Function must be called in the communication reset section.
|
||||
*
|
||||
* @param NMT This object will be initialized.
|
||||
* @param OD_1017_ProducerHbTime OD entry for 0x1017 -"Producer heartbeat time", entry is required, IO extension is
|
||||
* optional for runtime configuration.
|
||||
* @param em Emergency object.
|
||||
* @param nodeId CANopen Node ID of this device.
|
||||
* @param NMTcontrol Control variable for calculation of NMT internal state, based on error register, startup and
|
||||
* runtime behavior.
|
||||
* @param firstHBTime_ms Time between bootup and first heartbeat message in milliseconds. If firstHBTime_ms is greater
|
||||
* than "Producer Heartbeat time" (OD object 0x1017), latter is used instead. Entry is required, IO extension is
|
||||
* optional.
|
||||
* @param NMT_CANdevRx CAN device for NMT reception.
|
||||
* @param NMT_rxIdx Index of receive buffer in above CAN device.
|
||||
* @param CANidRxNMT CAN identifier for NMT receive message.
|
||||
* @param NMT_CANdevTx CAN device for NMT master transmission.
|
||||
* @param NMT_txIdx Index of transmit buffer in above CAN device.
|
||||
* @param CANidTxNMT CAN identifier for NMT transmit message.
|
||||
* @param HB_CANdevTx CAN device for HB transmission.
|
||||
* @param HB_txIdx Index of transmit buffer in the above CAN device.
|
||||
* @param CANidTxHB CAN identifier for HB message.
|
||||
* @param [out] errInfo Additional information in case of error, may be NULL.
|
||||
*
|
||||
* @return #CO_ReturnError_t CO_ERROR_NO on success.
|
||||
*/
|
||||
CO_ReturnError_t CO_NMT_init(CO_NMT_t* NMT, OD_entry_t* OD_1017_ProducerHbTime, CO_EM_t* em, uint8_t nodeId,
|
||||
uint16_t NMTcontrol, uint16_t firstHBTime_ms, CO_CANmodule_t* NMT_CANdevRx,
|
||||
uint16_t NMT_rxIdx, uint16_t CANidRxNMT,
|
||||
#if (((CO_CONFIG_NMT)&CO_CONFIG_NMT_MASTER) != 0) || defined CO_DOXYGEN
|
||||
CO_CANmodule_t* NMT_CANdevTx, uint16_t NMT_txIdx, uint16_t CANidTxNMT,
|
||||
#endif
|
||||
CO_CANmodule_t* HB_CANdevTx, uint16_t HB_txIdx, uint16_t CANidTxHB, uint32_t* errInfo);
|
||||
|
||||
#if (((CO_CONFIG_NMT)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Initialize NMT callback function after message preprocessed.
|
||||
*
|
||||
* Function initializes optional callback function, which should immediately start processing of CO_NMT_process()
|
||||
* function. Callback is called after NMT message is received from the CAN bus.
|
||||
*
|
||||
* @param NMT This object.
|
||||
* @param object Pointer to object, which will be passed to pFunctSignal(). Can be NULL
|
||||
* @param pFunctSignal Pointer to the callback function. Not called if NULL.
|
||||
*/
|
||||
void CO_NMT_initCallbackPre(CO_NMT_t* NMT, void* object, void (*pFunctSignal)(void* object));
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_NMT)&CO_CONFIG_NMT_CALLBACK_CHANGE) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Initialize NMT callback function.
|
||||
*
|
||||
* Function initializes optional callback function, which is called after NMT State change has occurred. Function may
|
||||
* wake up external task which handles NMT events. The first call is made immediately to give the consumer the current
|
||||
* NMT state.
|
||||
*
|
||||
* @param NMT This object.
|
||||
* @param pFunctNMT Pointer to the callback function. Not called if NULL.
|
||||
*/
|
||||
void CO_NMT_initCallbackChanged(CO_NMT_t* NMT, void (*pFunctNMT)(CO_NMT_internalState_t state));
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Process received NMT and produce Heartbeat messages.
|
||||
*
|
||||
* Function must be called cyclically.
|
||||
*
|
||||
* @param NMT This object.
|
||||
* @param [out] NMTstate If not NULL, CANopen NMT internal state is returned.
|
||||
* @param timeDifference_us Time difference from previous function call in microseconds.
|
||||
* @param [out] timerNext_us info to OS - see CO_process().
|
||||
*
|
||||
* @return #CO_NMT_reset_cmd_t
|
||||
*/
|
||||
CO_NMT_reset_cmd_t CO_NMT_process(CO_NMT_t* NMT, CO_NMT_internalState_t* NMTstate, uint32_t timeDifference_us,
|
||||
uint32_t* timerNext_us);
|
||||
|
||||
/**
|
||||
* Query current NMT state
|
||||
*
|
||||
* @param NMT This object.
|
||||
*
|
||||
* @return @ref CO_NMT_internalState_t
|
||||
*/
|
||||
static inline CO_NMT_internalState_t
|
||||
CO_NMT_getInternalState(CO_NMT_t* NMT) {
|
||||
return (NMT == NULL) ? CO_NMT_INITIALIZING : NMT->operatingState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send NMT command to self, without sending NMT message
|
||||
*
|
||||
* Internal NMT state will be verified and switched inside @ref CO_NMT_process()
|
||||
*
|
||||
* @param NMT This object.
|
||||
* @param command NMT command
|
||||
*/
|
||||
static inline void
|
||||
CO_NMT_sendInternalCommand(CO_NMT_t* NMT, CO_NMT_command_t command) {
|
||||
if (NMT != NULL) {
|
||||
NMT->internalCommand = command;
|
||||
}
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_NMT)&CO_CONFIG_NMT_MASTER) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Send NMT master command.
|
||||
*
|
||||
* This functionality may only be used from NMT master, as specified by standard CiA302-2. Standard provides one
|
||||
* exception, where application from slave node may send NMT master command: "If CANopen object 0x1F80 has value of
|
||||
* **0x2**, then NMT slave shall execute the NMT service start remote node (CO_NMT_ENTER_OPERATIONAL) with nodeID set to
|
||||
* 0."
|
||||
*
|
||||
* @param NMT This object.
|
||||
* @param command NMT command from CO_NMT_command_t.
|
||||
* @param nodeID Node ID of the remote node. 0 for all nodes including self.
|
||||
*
|
||||
* @return CO_ERROR_NO on success or CO_ReturnError_t from CO_CANsend().
|
||||
*/
|
||||
CO_ReturnError_t CO_NMT_sendCommand(CO_NMT_t* NMT, CO_NMT_command_t command, uint8_t nodeID);
|
||||
|
||||
#endif /* (CO_CONFIG_NMT) & CO_CONFIG_NMT_MASTER */
|
||||
|
||||
/** @} */ /* CO_NMT_Heartbeat */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CO_NMT_HEARTBEAT_H */
|
||||
390
Middleware/CANopenNode/301/CO_Node_Guarding.c
Normal file
390
Middleware/CANopenNode/301/CO_Node_Guarding.c
Normal file
@@ -0,0 +1,390 @@
|
||||
/*
|
||||
* CANopen Node Guarding slave and master objects.
|
||||
*
|
||||
* @file CO_Node_Guarding.c
|
||||
* @ingroup CO_Node_Guarding
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2023 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 "301/CO_Node_Guarding.h"
|
||||
|
||||
#if ((CO_CONFIG_NODE_GUARDING)&CO_CONFIG_NODE_GUARDING_SLAVE_ENABLE) != 0
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
static void
|
||||
CO_ngs_receive(void* object, void* msg) {
|
||||
(void)msg;
|
||||
CO_nodeGuardingSlave_t* ngs = (CO_nodeGuardingSlave_t*)object;
|
||||
|
||||
CO_FLAG_SET(ngs->CANrxNew);
|
||||
}
|
||||
|
||||
/*
|
||||
* Custom function for writing OD object "Guard time"
|
||||
*
|
||||
* For more information see file CO_ODinterface.h, OD_IO_t.
|
||||
*/
|
||||
static ODR_t
|
||||
OD_write_100C(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
|
||||
if ((stream == NULL) || (stream->subIndex != 0U) || (buf == NULL) || (count != sizeof(uint16_t))
|
||||
|| (countWritten == NULL)) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
CO_nodeGuardingSlave_t* ngs = (CO_nodeGuardingSlave_t*)stream->object;
|
||||
|
||||
/* update objects */
|
||||
ngs->guardTime_us = (uint32_t)CO_getUint16(buf) * 1000U;
|
||||
ngs->lifeTime_us = ngs->guardTime_us * ngs->lifeTimeFactor;
|
||||
|
||||
/* reset running timer */
|
||||
if (ngs->lifeTimer > 0U) {
|
||||
ngs->lifeTimer = ngs->lifeTime_us;
|
||||
}
|
||||
|
||||
/* write value to the original location in the Object Dictionary */
|
||||
return OD_writeOriginal(stream, buf, count, countWritten);
|
||||
}
|
||||
|
||||
/*
|
||||
* Custom function for writing OD object "Life time factor"
|
||||
*
|
||||
* For more information see file CO_ODinterface.h, OD_IO_t.
|
||||
*/
|
||||
static ODR_t
|
||||
OD_write_100D(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
|
||||
if ((stream == NULL) || (stream->subIndex != 0U) || (buf == NULL) || (count != sizeof(uint8_t))
|
||||
|| (countWritten == NULL)) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
CO_nodeGuardingSlave_t* ngs = (CO_nodeGuardingSlave_t*)stream->object;
|
||||
|
||||
/* update objects */
|
||||
ngs->lifeTimeFactor = (uint8_t)CO_getUint8(buf);
|
||||
ngs->lifeTime_us = ngs->guardTime_us * ngs->lifeTimeFactor;
|
||||
|
||||
/* reset running timer */
|
||||
if (ngs->lifeTimer > 0U) {
|
||||
ngs->lifeTimer = ngs->lifeTime_us;
|
||||
}
|
||||
|
||||
/* write value to the original location in the Object Dictionary */
|
||||
return OD_writeOriginal(stream, buf, count, countWritten);
|
||||
}
|
||||
|
||||
CO_ReturnError_t
|
||||
CO_nodeGuardingSlave_init(CO_nodeGuardingSlave_t* ngs, OD_entry_t* OD_100C_GuardTime,
|
||||
OD_entry_t* OD_100D_LifeTimeFactor, CO_EM_t* em, uint16_t CANidNodeGuarding,
|
||||
CO_CANmodule_t* CANdevRx, uint16_t CANdevRxIdx, CO_CANmodule_t* CANdevTx,
|
||||
uint16_t CANdevTxIdx, uint32_t* errInfo) {
|
||||
CO_ReturnError_t ret = CO_ERROR_NO;
|
||||
|
||||
/* verify arguments */
|
||||
if ((ngs == NULL) || (em == NULL) || (CANdevRx == NULL) || (CANdevTx == NULL) || (OD_100C_GuardTime == NULL)
|
||||
|| (OD_100D_LifeTimeFactor == NULL)) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* clear the object */
|
||||
(void)memset(ngs, 0, sizeof(CO_nodeGuardingSlave_t));
|
||||
|
||||
/* Configure object variables */
|
||||
ngs->em = em;
|
||||
|
||||
/* get and verify required "Guard time" from the Object Dictionary */
|
||||
uint16_t guardTime_ms;
|
||||
ODR_t odRet = OD_get_u16(OD_100C_GuardTime, 0, &guardTime_ms, true);
|
||||
if (odRet != ODR_OK) {
|
||||
if (errInfo != NULL) {
|
||||
*errInfo = OD_getIndex(OD_100C_GuardTime);
|
||||
}
|
||||
return CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
ngs->guardTime_us = (uint32_t)guardTime_ms * 1000U;
|
||||
|
||||
ngs->OD_100C_extension.object = ngs;
|
||||
ngs->OD_100C_extension.read = OD_readOriginal;
|
||||
ngs->OD_100C_extension.write = OD_write_100C;
|
||||
odRet = OD_extension_init(OD_100C_GuardTime, &ngs->OD_100C_extension);
|
||||
if (odRet != ODR_OK) {
|
||||
if (errInfo != NULL) {
|
||||
*errInfo = OD_getIndex(OD_100C_GuardTime);
|
||||
}
|
||||
return CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
|
||||
/* get and verify required "Life time factor" from the Object Dictionary */
|
||||
uint8_t lifeTimeFactor;
|
||||
odRet = OD_get_u8(OD_100D_LifeTimeFactor, 0, &lifeTimeFactor, true);
|
||||
if (odRet != ODR_OK) {
|
||||
if (errInfo != NULL) {
|
||||
*errInfo = OD_getIndex(OD_100D_LifeTimeFactor);
|
||||
}
|
||||
return CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
ngs->lifeTimeFactor = lifeTimeFactor;
|
||||
ngs->lifeTime_us = ngs->guardTime_us * ngs->lifeTimeFactor;
|
||||
|
||||
ngs->OD_100D_extension.object = ngs;
|
||||
ngs->OD_100D_extension.read = OD_readOriginal;
|
||||
ngs->OD_100D_extension.write = OD_write_100D;
|
||||
odRet = OD_extension_init(OD_100D_LifeTimeFactor, &ngs->OD_100D_extension);
|
||||
if (odRet != ODR_OK) {
|
||||
if (errInfo != NULL) {
|
||||
*errInfo = OD_getIndex(OD_100D_LifeTimeFactor);
|
||||
}
|
||||
return CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
|
||||
/* configure CAN reception */
|
||||
ret = CO_CANrxBufferInit(CANdevRx, CANdevRxIdx, CANidNodeGuarding, 0x7FF, true, (void*)ngs, CO_ngs_receive);
|
||||
if (ret != CO_ERROR_NO) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* configure CAN transmission */
|
||||
ngs->CANdevTx = CANdevTx;
|
||||
ngs->CANtxBuff = CO_CANtxBufferInit(CANdevTx, CANdevTxIdx, CANidNodeGuarding, false, 1, false);
|
||||
if (ngs->CANtxBuff == NULL) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
CO_nodeGuardingSlave_process(CO_nodeGuardingSlave_t* ngs, CO_NMT_internalState_t NMTstate, bool_t slaveDisable,
|
||||
uint32_t timeDifference_us, uint32_t* timerNext_us) {
|
||||
(void)timerNext_us; /* may be unused */
|
||||
|
||||
if (slaveDisable) {
|
||||
ngs->toggle = false;
|
||||
ngs->lifeTimer = 0;
|
||||
CO_FLAG_CLEAR(ngs->CANrxNew);
|
||||
return;
|
||||
}
|
||||
|
||||
/* was RTR just received */
|
||||
if (CO_FLAG_READ(ngs->CANrxNew)) {
|
||||
ngs->lifeTimer = ngs->lifeTime_us;
|
||||
|
||||
/* send response */
|
||||
ngs->CANtxBuff->data[0] = (uint8_t)NMTstate;
|
||||
if (ngs->toggle) {
|
||||
ngs->CANtxBuff->data[0] |= 0x80U;
|
||||
ngs->toggle = false;
|
||||
} else {
|
||||
ngs->toggle = true;
|
||||
}
|
||||
(void)CO_CANsend(ngs->CANdevTx, ngs->CANtxBuff);
|
||||
|
||||
if (ngs->lifeTimeTimeout) {
|
||||
/* error bit is shared with HB consumer */
|
||||
CO_errorReset(ngs->em, CO_EM_HEARTBEAT_CONSUMER, 0);
|
||||
ngs->lifeTimeTimeout = false;
|
||||
}
|
||||
|
||||
CO_FLAG_CLEAR(ngs->CANrxNew);
|
||||
}
|
||||
|
||||
/* verify "Life time" timeout and update the timer */
|
||||
else if (ngs->lifeTimer > 0U) {
|
||||
if (timeDifference_us < ngs->lifeTimer) {
|
||||
ngs->lifeTimer -= timeDifference_us;
|
||||
#if ((CO_CONFIG_NMT)&CO_CONFIG_FLAG_TIMERNEXT) != 0
|
||||
/* Calculate, when timeout expires */
|
||||
if (timerNext_us != NULL && *timerNext_us > ngs->lifeTimer) {
|
||||
*timerNext_us = ngs->lifeTimer;
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
ngs->lifeTimer = 0;
|
||||
ngs->lifeTimeTimeout = true;
|
||||
|
||||
/* error bit is shared with HB consumer */
|
||||
CO_errorReport(ngs->em, CO_EM_HEARTBEAT_CONSUMER, CO_EMC_HEARTBEAT, 0);
|
||||
}
|
||||
} else { /* MISRA C 2004 14.10 */
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#endif /* (CO_CONFIG_NODE_GUARDING) & CO_CONFIG_NODE_GUARDING_SLAVE_ENABLE */
|
||||
|
||||
#if ((CO_CONFIG_NODE_GUARDING)&CO_CONFIG_NODE_GUARDING_MASTER_ENABLE) != 0
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* Function receives messages from CAN identifier from 0x700 to 0x7FF. It
|
||||
* searches matching node->ident from nodes array.
|
||||
*/
|
||||
static void
|
||||
CO_ngm_receive(void* object, void* msg) {
|
||||
CO_nodeGuardingMaster_t* ngm = (CO_nodeGuardingMaster_t*)object;
|
||||
|
||||
uint8_t DLC = CO_CANrxMsg_readDLC(msg);
|
||||
const uint8_t* data = CO_CANrxMsg_readData(msg);
|
||||
uint16_t ident = CO_CANrxMsg_readIdent(msg);
|
||||
CO_nodeGuardingMasterNode_t* node = &ngm->nodes[0];
|
||||
|
||||
if (DLC == 1) {
|
||||
for (uint8_t i = 0; i < CO_CONFIG_NODE_GUARDING_MASTER_COUNT; i++) {
|
||||
if (ident == node->ident) {
|
||||
uint8_t toggle = data[0] & 0x80;
|
||||
if (toggle == node->toggle) {
|
||||
node->responseRecived = true;
|
||||
node->NMTstate = (CO_NMT_internalState_t)(data[0] & 0x7F);
|
||||
node->toggle = (toggle != 0) ? 0x00 : 0x80;
|
||||
}
|
||||
break;
|
||||
}
|
||||
node++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CO_ReturnError_t
|
||||
CO_nodeGuardingMaster_init(CO_nodeGuardingMaster_t* ngm, CO_EM_t* em, CO_CANmodule_t* CANdevRx, uint16_t CANdevRxIdx,
|
||||
CO_CANmodule_t* CANdevTx, uint16_t CANdevTxIdx) {
|
||||
CO_ReturnError_t ret = CO_ERROR_NO;
|
||||
|
||||
/* verify arguments */
|
||||
if (ngm == NULL || em == NULL || CANdevRx == NULL || CANdevTx == NULL) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* clear the object */
|
||||
(void)memset(ngm, 0, sizeof(CO_nodeGuardingMaster_t));
|
||||
|
||||
/* Configure object variables */
|
||||
ngm->em = em;
|
||||
|
||||
/* configure CAN reception. One buffer will receive all messages from CAN-id 0x700 to 0x7FF. */
|
||||
ret = CO_CANrxBufferInit(CANdevRx, CANdevRxIdx, CO_CAN_ID_HEARTBEAT, 0x780, false, (void*)ngm, CO_ngm_receive);
|
||||
if (ret != CO_ERROR_NO) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* configure CAN transmission */
|
||||
ngm->CANdevTx = CANdevTx;
|
||||
ngm->CANdevTxIdx = CANdevTxIdx;
|
||||
ngm->CANtxBuff = CO_CANtxBufferInit(CANdevTx, CANdevTxIdx, CO_CAN_ID_HEARTBEAT, true, 1, 0);
|
||||
if (ngm->CANtxBuff == NULL) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
CO_ReturnError_t
|
||||
CO_nodeGuardingMaster_initNode(CO_nodeGuardingMaster_t* ngm, uint8_t index, uint8_t nodeId, uint16_t guardTime_ms) {
|
||||
if (ngm == NULL || index >= CO_CONFIG_NODE_GUARDING_MASTER_COUNT || nodeId < 1 || nodeId > 0x7F) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
CO_nodeGuardingMasterNode_t* node = &ngm->nodes[index];
|
||||
|
||||
node->guardTime_us = (uint32_t)guardTime_ms * 1000;
|
||||
node->guardTimer = 0;
|
||||
node->ident = CO_CAN_ID_HEARTBEAT + nodeId;
|
||||
node->NMTstate = CO_NMT_UNKNOWN; /* for the first time */
|
||||
node->toggle = false;
|
||||
node->responseRecived = true; /* for the first time */
|
||||
node->CANtxWasBusy = false;
|
||||
node->monitoringActive = false;
|
||||
|
||||
#if CO_CONFIG_NODE_GUARDING_MASTER_COUNT == 1
|
||||
ngm->CANtxBuff = CO_CANtxBufferInit(ngm->CANdevTx, ngm->CANdevTxIdx, node->ident, true, 1, 0);
|
||||
#endif
|
||||
|
||||
return CO_ERROR_NO;
|
||||
}
|
||||
|
||||
void
|
||||
CO_nodeGuardingMaster_process(CO_nodeGuardingMaster_t* ngm, uint32_t timeDifference_us, uint32_t* timerNext_us) {
|
||||
(void)timerNext_us; /* may be unused */
|
||||
bool_t allMonitoredActiveCurrent = true;
|
||||
bool_t allMonitoredOperationalCurrent = true;
|
||||
CO_nodeGuardingMasterNode_t* node = &ngm->nodes[0];
|
||||
|
||||
for (uint8_t i = 0; i < CO_CONFIG_NODE_GUARDING_MASTER_COUNT; i++) {
|
||||
if (node->guardTime_us > 0 && node->ident > CO_CAN_ID_HEARTBEAT) {
|
||||
if (timeDifference_us < node->guardTimer) {
|
||||
node->guardTimer -= timeDifference_us;
|
||||
#if ((CO_CONFIG_NMT)&CO_CONFIG_FLAG_TIMERNEXT) != 0
|
||||
/* Calculate, when timeout expires */
|
||||
if (timerNext_us != NULL && *timerNext_us > node->guardTimer) {
|
||||
*timerNext_us = node->guardTimer;
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
/* it is time to send new rtr, but first verify last response */
|
||||
if (!node->CANtxWasBusy) {
|
||||
if (!node->responseRecived) {
|
||||
node->monitoringActive = false;
|
||||
/* error bit is shared with HB consumer */
|
||||
CO_errorReport(ngm->em, CO_EM_HEARTBEAT_CONSUMER, CO_EMC_HEARTBEAT, node->ident & 0x7F);
|
||||
} else if (node->NMTstate != CO_NMT_UNKNOWN) {
|
||||
node->monitoringActive = true;
|
||||
CO_errorReset(ngm->em, CO_EM_HEARTBEAT_CONSUMER, node->ident & 0x7F);
|
||||
}
|
||||
}
|
||||
|
||||
if (ngm->CANtxBuff->bufferFull) {
|
||||
node->guardTimer = 0;
|
||||
node->CANtxWasBusy = true;
|
||||
} else {
|
||||
#if CO_CONFIG_NODE_GUARDING_MASTER_COUNT > 1
|
||||
ngm->CANtxBuff = CO_CANtxBufferInit(ngm->CANdevTx, ngm->CANdevTxIdx, node->ident, true, 1, 0);
|
||||
#endif
|
||||
(void)CO_CANsend(ngm->CANdevTx, ngm->CANtxBuff);
|
||||
node->CANtxWasBusy = false;
|
||||
node->responseRecived = false;
|
||||
node->guardTimer = node->guardTime_us;
|
||||
}
|
||||
}
|
||||
|
||||
if (allMonitoredActiveCurrent) {
|
||||
if (node->monitoringActive) {
|
||||
if (node->NMTstate != CO_NMT_OPERATIONAL) {
|
||||
allMonitoredOperationalCurrent = false;
|
||||
}
|
||||
} else {
|
||||
allMonitoredActiveCurrent = false;
|
||||
allMonitoredOperationalCurrent = false;
|
||||
}
|
||||
}
|
||||
} /* if node enabled */
|
||||
|
||||
node++;
|
||||
} /* for */
|
||||
|
||||
ngm->allMonitoredActive = allMonitoredActiveCurrent;
|
||||
ngm->allMonitoredOperational = allMonitoredOperationalCurrent;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#endif /* (CO_CONFIG_NODE_GUARDING) & CO_CONFIG_NODE_GUARDING_MASTER_ENABLE */
|
||||
244
Middleware/CANopenNode/301/CO_Node_Guarding.h
Normal file
244
Middleware/CANopenNode/301/CO_Node_Guarding.h
Normal file
@@ -0,0 +1,244 @@
|
||||
/**
|
||||
* CANopen Node Guarding slave and master objects.
|
||||
*
|
||||
* @file CO_Node_Guarding.h
|
||||
* @ingroup CO_Node_Guarding
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2023 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.
|
||||
*/
|
||||
|
||||
#ifndef CO_NODE_GUARDING_H
|
||||
#define CO_NODE_GUARDING_H
|
||||
|
||||
#include "301/CO_driver.h"
|
||||
#include "301/CO_ODinterface.h"
|
||||
#include "301/CO_Emergency.h"
|
||||
#include "301/CO_NMT_Heartbeat.h"
|
||||
|
||||
/* default configuration, see CO_config.h */
|
||||
#ifndef CO_CONFIG_NODE_GUARDING
|
||||
#define CO_CONFIG_NODE_GUARDING (0)
|
||||
#endif
|
||||
#ifndef CO_CONFIG_NODE_GUARDING_MASTER_COUNT
|
||||
#define CO_CONFIG_NODE_GUARDING_MASTER_COUNT 0x7F
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_NODE_GUARDING)&CO_CONFIG_NODE_GUARDING_SLAVE_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_Node_Guarding Node Guarding CANopen Node Guarding, an older alternative to the Heartbeat protocol.
|
||||
*
|
||||
* @ingroup CO_CANopen_301
|
||||
* @{
|
||||
* Node guarding master pools each node guarding slave at time intervals, called guard time. Master sends a CAN RTR
|
||||
* message, and slave responds. Slave also monitors presence of RTR message from master and indicates error, if it
|
||||
* wasn't received within life time. ('Life time' is 'Guard time' multiplied by 'Life time factor').
|
||||
*
|
||||
* Adding Node guarding to the project:
|
||||
* - Make sure, driver supports it. RTR bit should be part of CAN identifier.
|
||||
* - Enable it with 'CO_CONFIG_NODE_GUARDING', see CO_config.h
|
||||
* - For slave add 0x100C and 0x100D objects to the Object dictionary.
|
||||
* - For master use CO_nodeGuardingMaster_initNode() to add monitored nodes.
|
||||
*
|
||||
* @warning Usage of Node guarding is not recommended, as it is outdated and uses RTR CAN functionality, which is also
|
||||
* not recommended. Use Heartbeat and Heartbeat consumer, if possible.
|
||||
*
|
||||
* ### Node Guarding slave response message contents:
|
||||
*
|
||||
* Byte, bits | Description
|
||||
* ---------------|-----------------------------------------------------------
|
||||
* 0, bits 0..6 | @ref CO_NMT_internalState_t
|
||||
* 0, bit 7 | toggle bit
|
||||
*
|
||||
* See @ref CO_Default_CAN_ID_t for CAN identifiers.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Node Guarding slave object
|
||||
*/
|
||||
typedef struct {
|
||||
CO_EM_t* em; /**< From CO_nodeGuardingSlave_init() */
|
||||
volatile void* CANrxNew; /**< Indicates, if new rtr message received from CAN bus */
|
||||
uint32_t guardTime_us; /**< Guard time in microseconds, calculated from OD_0x100C */
|
||||
uint32_t lifeTime_us; /**< Life time in microseconds, calculated from guardTime_us * lifeTimeFactor */
|
||||
uint32_t lifeTimer; /**< Timer for monitoring Life time, counting down from lifeTime_us. */
|
||||
uint8_t lifeTimeFactor; /**< Life time factor, from OD_0x100D */
|
||||
bool_t toggle; /**< Toggle bit for response */
|
||||
bool_t lifeTimeTimeout; /**< True if rtr from master is missing */
|
||||
OD_extension_t OD_100C_extension; /**< Extension for OD object */
|
||||
OD_extension_t OD_100D_extension; /**< Extension for OD object */
|
||||
CO_CANmodule_t* CANdevTx; /**< From CO_nodeGuardingSlave_init() */
|
||||
CO_CANtx_t* CANtxBuff; /**< CAN transmit buffer for the message */
|
||||
} CO_nodeGuardingSlave_t;
|
||||
|
||||
/**
|
||||
* Initialize Node Guarding slave object.
|
||||
*
|
||||
* Function must be called in the communication reset section.
|
||||
*
|
||||
* @param ngs This object will be initialized.
|
||||
* @param OD_100C_GuardTime OD entry for 0x100C -"Guard time", entry is required.
|
||||
* @param OD_100D_LifeTimeFactor OD entry for 0x100D -"Life time factor", entry is required.
|
||||
* @param em Emergency object.
|
||||
* @param CANidNodeGuarding CAN identifier for Node Guarding rtr and response message (usually CO_CAN_ID_HEARTBEAT +
|
||||
* nodeId).
|
||||
* @param CANdevRx CAN device for Node Guarding rtr reception.
|
||||
* @param CANdevRxIdx Index of the receive buffer in the above CAN device.
|
||||
* @param CANdevTx CAN device for Node Guarding response transmission.
|
||||
* @param CANdevTxIdx Index of the transmit buffer in the above CAN device.
|
||||
* @param [out] errInfo Additional information in case of error, may be NULL.
|
||||
*
|
||||
* @return #CO_ReturnError_t CO_ERROR_NO on success.
|
||||
*/
|
||||
CO_ReturnError_t CO_nodeGuardingSlave_init(CO_nodeGuardingSlave_t* ngs, OD_entry_t* OD_100C_GuardTime,
|
||||
OD_entry_t* OD_100D_LifeTimeFactor, CO_EM_t* em, uint16_t CANidNodeGuarding,
|
||||
CO_CANmodule_t* CANdevRx, uint16_t CANdevRxIdx, CO_CANmodule_t* CANdevTx,
|
||||
uint16_t CANdevTxIdx, uint32_t* errInfo);
|
||||
|
||||
/**
|
||||
* Process Node Guarding slave.
|
||||
*
|
||||
* Function must be called cyclically.
|
||||
*
|
||||
* @param ngs This object.
|
||||
* @param NMTstate NMT operating state.
|
||||
* @param slaveDisable If true, then Node guarding slave is disabled.
|
||||
* @param timeDifference_us Time difference from previous function call in microseconds.
|
||||
* @param [out] timerNext_us info to OS - see CO_process().
|
||||
*/
|
||||
void CO_nodeGuardingSlave_process(CO_nodeGuardingSlave_t* ngs, CO_NMT_internalState_t NMTstate, bool_t slaveDisable,
|
||||
uint32_t timeDifference_us, uint32_t* timerNext_us);
|
||||
|
||||
/**
|
||||
* Inquire, if Node guarding slave detected life time timeout
|
||||
*
|
||||
* Error is reset after pool request from master.
|
||||
*
|
||||
* @param ngs This object.
|
||||
*
|
||||
* @return true, if life time timeout was detected.
|
||||
*/
|
||||
static inline bool_t
|
||||
CO_nodeGuardingSlave_isTimeout(CO_nodeGuardingSlave_t* ngs) {
|
||||
return (ngs == NULL) || ngs->lifeTimeTimeout;
|
||||
}
|
||||
|
||||
/** @} */ /* CO_Node_Guarding */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_NODE_GUARDING) & CO_CONFIG_NODE_GUARDING_SLAVE_ENABLE */
|
||||
|
||||
#if (((CO_CONFIG_NODE_GUARDING)&CO_CONFIG_NODE_GUARDING_MASTER_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
|
||||
#if CO_CONFIG_NODE_GUARDING_MASTER_COUNT < 1 || CO_CONFIG_NODE_GUARDING_MASTER_COUNT > 127
|
||||
#error CO_CONFIG_NODE_GUARDING_MASTER_COUNT value is wrong!
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @addtogroup CO_Node_Guarding
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Node Guarding master - monitored node
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t guardTime_us; /**< Guard time in microseconds */
|
||||
uint32_t guardTimer; /**< Guard timer in microseconds, counting down */
|
||||
uint16_t ident; /**< CAN identifier (CO_CAN_ID_HEARTBEAT + Node Id) */
|
||||
CO_NMT_internalState_t NMTstate; /**< NMT operating state */
|
||||
uint8_t toggle; /**< toggle bit7, expected from the next received message */
|
||||
bool_t responseRecived; /**< True, if response was received since last rtr message */
|
||||
bool_t CANtxWasBusy; /**< True, if CANtxBuff was busy since last processing */
|
||||
bool_t monitoringActive; /**< True, if monitoring is active (response within time). */
|
||||
} CO_nodeGuardingMasterNode_t;
|
||||
|
||||
/**
|
||||
* Node Guarding master object
|
||||
*/
|
||||
typedef struct {
|
||||
CO_EM_t* em; /**< From CO_nodeGuardingMaster_init() */
|
||||
CO_CANmodule_t* CANdevTx; /**< From CO_nodeGuardingMaster_init() */
|
||||
uint16_t CANdevTxIdx; /**< From CO_nodeGuardingMaster_init() */
|
||||
CO_CANtx_t* CANtxBuff; /**< CAN transmit buffer for the message */
|
||||
bool_t allMonitoredActive; /**< True, if all monitored nodes are active or no node is monitored. Can be read by the
|
||||
application */
|
||||
bool_t allMonitoredOperational; /**< True, if all monitored nodes are NMT operational or no node is monitored. Can
|
||||
be read by the application */
|
||||
CO_nodeGuardingMasterNode_t nodes[CO_CONFIG_NODE_GUARDING_MASTER_COUNT]; /**< Array of monitored nodes */
|
||||
} CO_nodeGuardingMaster_t;
|
||||
|
||||
/**
|
||||
* Initialize Node Guarding master object.
|
||||
*
|
||||
* Function must be called in the communication reset section.
|
||||
*
|
||||
* @param ngm This object will be initialized.
|
||||
* @param em Emergency object.
|
||||
* @param CANdevRx CAN device for Node Guarding reception.
|
||||
* @param CANdevRxIdx Index of the receive buffer in the above CAN device.
|
||||
* @param CANdevTx CAN device for Node Guarding rtr transmission.
|
||||
* @param CANdevTxIdx Index of the transmit buffer in the above CAN device.
|
||||
*
|
||||
* @return #CO_ReturnError_t CO_ERROR_NO on success.
|
||||
*/
|
||||
CO_ReturnError_t CO_nodeGuardingMaster_init(CO_nodeGuardingMaster_t* ngm, CO_EM_t* em, CO_CANmodule_t* CANdevRx,
|
||||
uint16_t CANdevRxIdx, CO_CANmodule_t* CANdevTx, uint16_t CANdevTxIdx);
|
||||
|
||||
/**
|
||||
* Initialize node inside Node Guarding master object.
|
||||
*
|
||||
* Function may be called any time after CO_nodeGuardingMaster_init(). It configures monitoring of the remote node.
|
||||
*
|
||||
* @param ngm Node Guarding master object.
|
||||
* @param index Index of the slot, which will be configured. 0 <= index < CO_CONFIG_NODE_GUARDING_MASTER_COUNT.
|
||||
* @param nodeId Node Id of the monitored node.
|
||||
* @param guardTime_ms Guard time of the monitored node.
|
||||
*
|
||||
* @return #CO_ReturnError_t CO_ERROR_NO on success.
|
||||
*/
|
||||
CO_ReturnError_t CO_nodeGuardingMaster_initNode(CO_nodeGuardingMaster_t* ngm, uint8_t index, uint8_t nodeId,
|
||||
uint16_t guardTime_ms);
|
||||
|
||||
/**
|
||||
* Process Node Guarding master.
|
||||
*
|
||||
* Function must be called cyclically.
|
||||
*
|
||||
* @param ngm This object.
|
||||
* @param timeDifference_us Time difference from previous function call in microseconds.
|
||||
* @param [out] timerNext_us info to OS - see CO_process().
|
||||
*/
|
||||
void CO_nodeGuardingMaster_process(CO_nodeGuardingMaster_t* ngm, uint32_t timeDifference_us, uint32_t* timerNext_us);
|
||||
|
||||
/** @} */ /* @addtogroup CO_Node_Guarding */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_NODE_GUARDING) & CO_CONFIG_NODE_GUARDING_MASTER_ENABLE */
|
||||
|
||||
#endif /* CO_NODE_GUARDING_H */
|
||||
371
Middleware/CANopenNode/301/CO_ODinterface.c
Normal file
371
Middleware/CANopenNode/301/CO_ODinterface.c
Normal file
@@ -0,0 +1,371 @@
|
||||
/*
|
||||
* CANopen Object Dictionary interface
|
||||
*
|
||||
* @file CO_ODinterface.c
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2020 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>
|
||||
#define OD_DEFINITION
|
||||
#include "301/CO_ODinterface.h"
|
||||
|
||||
ODR_t
|
||||
OD_readOriginal(OD_stream_t* stream, void* buf, OD_size_t count, OD_size_t* countRead) {
|
||||
if ((stream == NULL) || (buf == NULL) || (countRead == NULL)) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
OD_size_t dataLenToCopy = stream->dataLength; /* length of OD variable */
|
||||
const uint8_t* dataOrig = stream->dataOrig;
|
||||
|
||||
if (dataOrig == NULL) {
|
||||
return ODR_SUB_NOT_EXIST;
|
||||
}
|
||||
|
||||
ODR_t returnCode = ODR_OK;
|
||||
|
||||
/* If previous read was partial or OD variable length is larger than
|
||||
* current buffer size, then data was (will be) read in several segments */
|
||||
if ((stream->dataOffset > 0U) || (dataLenToCopy > count)) {
|
||||
if (stream->dataOffset >= dataLenToCopy) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
/* Reduce for already copied data */
|
||||
dataLenToCopy -= stream->dataOffset;
|
||||
dataOrig += stream->dataOffset;
|
||||
|
||||
if (dataLenToCopy > count) {
|
||||
/* Not enough space in destination buffer */
|
||||
dataLenToCopy = count;
|
||||
stream->dataOffset += dataLenToCopy;
|
||||
returnCode = ODR_PARTIAL;
|
||||
} else {
|
||||
stream->dataOffset = 0; /* copy finished, reset offset */
|
||||
}
|
||||
}
|
||||
|
||||
(void)memcpy((void*)buf, (const void*)dataOrig, dataLenToCopy);
|
||||
|
||||
*countRead = dataLenToCopy;
|
||||
return returnCode;
|
||||
}
|
||||
|
||||
ODR_t
|
||||
OD_writeOriginal(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
|
||||
if ((stream == NULL) || (buf == NULL) || (countWritten == NULL)) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
OD_size_t dataLenToCopy = stream->dataLength; /* length of OD variable */
|
||||
OD_size_t dataLenRemain = dataLenToCopy; /* remaining length of dataOrig buffer */
|
||||
uint8_t* dataOrig = stream->dataOrig;
|
||||
|
||||
if (dataOrig == NULL) {
|
||||
return ODR_SUB_NOT_EXIST;
|
||||
}
|
||||
|
||||
ODR_t returnCode = ODR_OK;
|
||||
|
||||
/* If previous write was partial or OD variable length is larger than current buffer size,
|
||||
* then data was (will be) written in several segments */
|
||||
if ((stream->dataOffset > 0U) || (dataLenToCopy > count)) {
|
||||
if (stream->dataOffset >= dataLenToCopy) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
/* reduce for already copied data */
|
||||
dataLenToCopy -= stream->dataOffset;
|
||||
dataLenRemain = dataLenToCopy;
|
||||
dataOrig += stream->dataOffset;
|
||||
|
||||
if (dataLenToCopy > count) {
|
||||
/* Remaining data space in OD variable is larger than current count
|
||||
* of data, so only current count of data will be copied */
|
||||
dataLenToCopy = count;
|
||||
stream->dataOffset += dataLenToCopy;
|
||||
returnCode = ODR_PARTIAL;
|
||||
} else {
|
||||
stream->dataOffset = 0; /* copy finished, reset offset */
|
||||
}
|
||||
}
|
||||
|
||||
if (dataLenToCopy < count) {
|
||||
/* OD variable is smaller than current amount of data */
|
||||
return ODR_DATA_LONG;
|
||||
}
|
||||
|
||||
/* additional check for Misra c compliance */
|
||||
if ((dataLenToCopy <= dataLenRemain) && (dataLenToCopy <= count)) {
|
||||
(void)memcpy((void*)dataOrig, (const void*)buf, dataLenToCopy);
|
||||
} else {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
*countWritten = dataLenToCopy;
|
||||
return returnCode;
|
||||
}
|
||||
|
||||
/* Read value from variable from Object Dictionary disabled, see OD_IO_t */
|
||||
static ODR_t
|
||||
OD_readDisabled(OD_stream_t* stream, void* buf, OD_size_t count, OD_size_t* countRead) {
|
||||
(void)stream;
|
||||
(void)buf;
|
||||
(void)count;
|
||||
(void)countRead;
|
||||
return ODR_UNSUPP_ACCESS;
|
||||
}
|
||||
|
||||
/* Write value to variable from Object Dictionary disabled, see OD_IO_t */
|
||||
static ODR_t
|
||||
OD_writeDisabled(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
|
||||
(void)stream;
|
||||
(void)buf;
|
||||
(void)count;
|
||||
(void)countWritten;
|
||||
return ODR_UNSUPP_ACCESS;
|
||||
}
|
||||
|
||||
OD_entry_t*
|
||||
OD_find(OD_t* od, uint16_t index) {
|
||||
if ((od == NULL) || (od->size == 0U)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint16_t min = 0;
|
||||
uint16_t max = od->size - 1U;
|
||||
|
||||
/* Fast search in ordered Object Dictionary. If indexes are mixed, this won't work. If Object
|
||||
* Dictionary has up to N entries, then the max number of loop passes is log2(N) */
|
||||
while (min < max) {
|
||||
/* get entry between min and max */
|
||||
uint16_t cur = (min + max) >> 1;
|
||||
OD_entry_t* entry = &od->list[cur];
|
||||
|
||||
if (index == entry->index) {
|
||||
return entry;
|
||||
}
|
||||
|
||||
if (index < entry->index) {
|
||||
max = (cur > 0U) ? (cur - 1U) : cur;
|
||||
} else {
|
||||
min = cur + 1U;
|
||||
}
|
||||
}
|
||||
|
||||
if (min == max) {
|
||||
OD_entry_t* entry = &od->list[min];
|
||||
if (index == entry->index) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL; /* entry does not exist in OD */
|
||||
}
|
||||
|
||||
ODR_t
|
||||
OD_getSub(const OD_entry_t* entry, uint8_t subIndex, OD_IO_t* io, bool_t odOrig) {
|
||||
if ((entry == NULL) || (entry->odObject == NULL)) {
|
||||
return ODR_IDX_NOT_EXIST;
|
||||
}
|
||||
if (io == NULL) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
ODR_t ret = ODR_OK;
|
||||
OD_stream_t* stream = &io->stream;
|
||||
|
||||
/* attribute, dataOrig and dataLength, depends on object type */
|
||||
switch (entry->odObjectType & (uint8_t)ODT_TYPE_MASK) {
|
||||
case ODT_VAR: {
|
||||
if (subIndex > 0U) {
|
||||
ret = ODR_SUB_NOT_EXIST;
|
||||
break;
|
||||
}
|
||||
CO_PROGMEM OD_obj_var_t* odo = entry->odObject;
|
||||
|
||||
stream->attribute = odo->attribute;
|
||||
stream->dataOrig = odo->dataOrig;
|
||||
stream->dataLength = odo->dataLength;
|
||||
break;
|
||||
}
|
||||
case ODT_ARR: {
|
||||
if (subIndex >= entry->subEntriesCount) {
|
||||
ret = ODR_SUB_NOT_EXIST;
|
||||
break;
|
||||
}
|
||||
CO_PROGMEM OD_obj_array_t* odo = entry->odObject;
|
||||
|
||||
if (subIndex == 0U) {
|
||||
stream->attribute = odo->attribute0;
|
||||
stream->dataOrig = odo->dataOrig0;
|
||||
stream->dataLength = 1;
|
||||
} else {
|
||||
stream->attribute = odo->attribute;
|
||||
uint8_t* ptr = odo->dataOrig;
|
||||
stream->dataOrig = (ptr == NULL) ? ptr : (ptr + (odo->dataElementSizeof * (uint8_t)(subIndex - 1U)));
|
||||
stream->dataLength = odo->dataElementLength;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ODT_REC: {
|
||||
CO_PROGMEM OD_obj_record_t* odoArr = entry->odObject;
|
||||
CO_PROGMEM OD_obj_record_t* odo = NULL;
|
||||
for (uint8_t i = 0; i < entry->subEntriesCount; i++) {
|
||||
if (odoArr[i].subIndex == subIndex) {
|
||||
odo = &odoArr[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (odo == NULL) {
|
||||
ret = ODR_SUB_NOT_EXIST;
|
||||
break;
|
||||
}
|
||||
|
||||
stream->attribute = odo->attribute;
|
||||
stream->dataOrig = odo->dataOrig;
|
||||
stream->dataLength = odo->dataLength;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
ret = ODR_DEV_INCOMPAT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret == ODR_OK) {
|
||||
/* Access data from the original OD location */
|
||||
if ((entry->extension == NULL) || odOrig) {
|
||||
io->read = OD_readOriginal;
|
||||
io->write = OD_writeOriginal;
|
||||
stream->object = NULL;
|
||||
}
|
||||
/* Access data from extension specified by application */
|
||||
else {
|
||||
io->read = (entry->extension->read != NULL) ? entry->extension->read : OD_readDisabled;
|
||||
io->write = (entry->extension->write != NULL) ? entry->extension->write : OD_writeDisabled;
|
||||
stream->object = entry->extension->object;
|
||||
}
|
||||
|
||||
/* Reset stream data offset */
|
||||
stream->dataOffset = 0;
|
||||
|
||||
/* Add informative data */
|
||||
stream->index = entry->index;
|
||||
stream->subIndex = subIndex;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
OD_getSDOabCode(ODR_t returnCode) {
|
||||
static const uint32_t abortCodes[(uint8_t)ODR_COUNT] = {
|
||||
0x00000000UL, /* No abort */
|
||||
0x05040005UL, /* Out of memory */
|
||||
0x06010000UL, /* Unsupported access to an object */
|
||||
0x06010001UL, /* Attempt to read a write only object */
|
||||
0x06010002UL, /* Attempt to write a read only object */
|
||||
0x06020000UL, /* Object does not exist in the object dictionary */
|
||||
0x06040041UL, /* Object cannot be mapped to the PDO */
|
||||
0x06040042UL, /* Num and len of object to be mapped exceeds PDO len */
|
||||
0x06040043UL, /* General parameter incompatibility reasons */
|
||||
0x06040047UL, /* General internal incompatibility in device */
|
||||
0x06060000UL, /* Access failed due to hardware error */
|
||||
0x06070010UL, /* Data type does not match, length does not match */
|
||||
0x06070012UL, /* Data type does not match, length too high */
|
||||
0x06070013UL, /* Data type does not match, length too short */
|
||||
0x06090011UL, /* Sub index does not exist */
|
||||
0x06090030UL, /* Invalid value for parameter (download only). */
|
||||
0x06090031UL, /* Value range of parameter written too high */
|
||||
0x06090032UL, /* Value range of parameter written too low */
|
||||
0x06090036UL, /* Maximum value is less than minimum value. */
|
||||
0x060A0023UL, /* Resource not available: SDO connection */
|
||||
0x08000000UL, /* General error */
|
||||
0x08000020UL, /* Data cannot be transferred or stored to application */
|
||||
0x08000021UL, /* Data cannot be transferred because of local control */
|
||||
0x08000022UL, /* Data cannot be tran. because of present device state */
|
||||
0x08000023UL, /* Object dict. not present or dynamic generation fails */
|
||||
0x08000024UL /* No data available */
|
||||
};
|
||||
|
||||
return ((returnCode < ODR_OK) || (returnCode >= ODR_COUNT)) ? abortCodes[ODR_DEV_INCOMPAT] : abortCodes[returnCode];
|
||||
}
|
||||
|
||||
ODR_t
|
||||
OD_get_value(const OD_entry_t* entry, uint8_t subIndex, void* val, OD_size_t len, bool_t odOrig) {
|
||||
if (val == NULL) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
OD_IO_t io = {NULL};
|
||||
OD_stream_t* stream = &io.stream;
|
||||
OD_size_t countRd = 0;
|
||||
|
||||
ODR_t ret = OD_getSub(entry, subIndex, &io, odOrig);
|
||||
|
||||
if (ret != ODR_OK) {
|
||||
return ret;
|
||||
}
|
||||
if (stream->dataLength != len) {
|
||||
return ODR_TYPE_MISMATCH;
|
||||
}
|
||||
|
||||
return io.read(stream, val, len, &countRd);
|
||||
}
|
||||
|
||||
ODR_t
|
||||
OD_set_value(const OD_entry_t* entry, uint8_t subIndex, void* val, OD_size_t len, bool_t odOrig) {
|
||||
if (val == NULL) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
OD_IO_t io = {NULL};
|
||||
OD_stream_t* stream = &io.stream;
|
||||
OD_size_t countWritten = 0;
|
||||
|
||||
ODR_t ret = OD_getSub(entry, subIndex, &io, odOrig);
|
||||
|
||||
if (ret != ODR_OK) {
|
||||
return ret;
|
||||
}
|
||||
if (stream->dataLength != len) {
|
||||
return ODR_TYPE_MISMATCH;
|
||||
}
|
||||
|
||||
return io.write(stream, val, len, &countWritten);
|
||||
}
|
||||
|
||||
void*
|
||||
OD_getPtr(const OD_entry_t* entry, uint8_t subIndex, OD_size_t len, ODR_t* err) {
|
||||
ODR_t errCopy;
|
||||
OD_IO_t io;
|
||||
OD_stream_t* stream = &io.stream;
|
||||
|
||||
errCopy = OD_getSub(entry, subIndex, &io, true);
|
||||
|
||||
if (errCopy == ODR_OK) {
|
||||
if ((stream->dataOrig == NULL) || (stream->dataLength == 0U)) {
|
||||
errCopy = ODR_DEV_INCOMPAT;
|
||||
} else if ((len != 0U) && (len != stream->dataLength)) {
|
||||
errCopy = ODR_TYPE_MISMATCH;
|
||||
} else { /* MISRA C 2004 14.10 */
|
||||
}
|
||||
}
|
||||
|
||||
if (err != NULL) {
|
||||
*err = errCopy;
|
||||
}
|
||||
|
||||
return (errCopy == ODR_OK) ? stream->dataOrig : NULL;
|
||||
}
|
||||
711
Middleware/CANopenNode/301/CO_ODinterface.h
Normal file
711
Middleware/CANopenNode/301/CO_ODinterface.h
Normal file
@@ -0,0 +1,711 @@
|
||||
/**
|
||||
* CANopen Object Dictionary interface
|
||||
*
|
||||
* @file CO_ODinterface.h
|
||||
* @ingroup CO_ODinterface
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2020 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.
|
||||
*/
|
||||
|
||||
#ifndef CO_OD_INTERFACE_H
|
||||
#define CO_OD_INTERFACE_H
|
||||
|
||||
#include "301/CO_driver.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_ODinterface OD interface
|
||||
* CANopen Object Dictionary interface.
|
||||
*
|
||||
* @ingroup CO_CANopen_301
|
||||
* @{
|
||||
* See @ref md_doc_2objectDictionary
|
||||
*/
|
||||
|
||||
#ifndef CO_OD_OWN_TYPES
|
||||
typedef uint32_t OD_size_t; /**< Variable of type OD_size_t contains data length in bytes of OD variable */
|
||||
typedef uint8_t OD_attr_t; /**< Type (and size) of Object Dictionary attribute */
|
||||
#endif
|
||||
|
||||
#ifndef OD_FLAGS_PDO_SIZE
|
||||
#define OD_FLAGS_PDO_SIZE 4U /**< Size of of flagsPDO variable inside @ref OD_extension_t, from 0 to 32. */
|
||||
#endif
|
||||
|
||||
#ifndef CO_PROGMEM
|
||||
/** Modifier for OD objects. This is large amount of data and is specified in Object Dictionary (OD.c file usually) */
|
||||
#define CO_PROGMEM const
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Common DS301 object dictionary entries.
|
||||
*/
|
||||
typedef enum {
|
||||
OD_H1000_DEV_TYPE = 0x1000U, /**< Device type */
|
||||
OD_H1001_ERR_REG = 0x1001U, /**< Error register */
|
||||
OD_H1002_MANUF_STATUS_REG = 0x1002U, /**< Manufacturer status register */
|
||||
OD_H1003_PREDEF_ERR_FIELD = 0x1003U, /**< Predefined error field */
|
||||
OD_H1004_RSV = 0x1004U, /**< Reserved */
|
||||
OD_H1005_COBID_SYNC = 0x1005U, /**< Sync message cob-id */
|
||||
OD_H1006_COMM_CYCL_PERIOD = 0x1006U, /**< Communication cycle period */
|
||||
OD_H1007_SYNC_WINDOW_LEN = 0x1007U, /**< Sync windows length */
|
||||
OD_H1008_MANUF_DEV_NAME = 0x1008U, /**< Manufacturer device name */
|
||||
OD_H1009_MANUF_HW_VERSION = 0x1009U, /**< Manufacturer hardware version */
|
||||
OD_H100A_MANUF_SW_VERSION = 0x100AU, /**< Manufacturer software version */
|
||||
OD_H100B_RSV = 0x100BU, /**< Reserved */
|
||||
OD_H100C_GUARD_TIME = 0x100CU, /**< Guard time */
|
||||
OD_H100D_LIFETIME_FACTOR = 0x100DU, /**< Life time factor */
|
||||
OD_H100E_RSV = 0x100EU, /**< Reserved */
|
||||
OD_H100F_RSV = 0x100FU, /**< Reserved */
|
||||
OD_H1010_STORE_PARAMETERS = 0x1010U, /**< Store params in persistent mem. */
|
||||
OD_H1011_RESTORE_DEFAULT = 0x1011U, /**< Restore default parameters */
|
||||
OD_H1012_COBID_TIME = 0x1012U, /**< Timestamp message cob-id */
|
||||
OD_H1013_HIGH_RES_TIMESTAMP = 0x1013U, /**< High resolution timestamp */
|
||||
OD_H1014_COBID_EMERGENCY = 0x1014U, /**< Emergency message cob-id */
|
||||
OD_H1015_INHIBIT_TIME_EMCY = 0x1015U, /**< Inhibit time emergency message */
|
||||
OD_H1016_CONSUMER_HB_TIME = 0x1016U, /**< Consumer heartbeat time */
|
||||
OD_H1017_PRODUCER_HB_TIME = 0x1017U, /**< Producer heartbeat time */
|
||||
OD_H1018_IDENTITY_OBJECT = 0x1018U, /**< Identity object */
|
||||
OD_H1019_SYNC_CNT_OVERFLOW = 0x1019U, /**< Sync counter overflow value */
|
||||
OD_H1020_VERIFY_CONFIG = 0x1020U, /**< Verify configuration */
|
||||
OD_H1021_STORE_EDS = 0x1021U, /**< Store EDS */
|
||||
OD_H1022_STORE_FORMAT = 0x1022U, /**< Store format */
|
||||
OD_H1023_OS_CMD = 0x1023U, /**< OS command */
|
||||
OD_H1024_OS_CMD_MODE = 0x1024U, /**< OS command mode */
|
||||
OD_H1025_OS_DBG_INTERFACE = 0x1025U, /**< OS debug interface */
|
||||
OD_H1026_OS_PROMPT = 0x1026U, /**< OS prompt */
|
||||
OD_H1027_MODULE_LIST = 0x1027U, /**< Module list */
|
||||
OD_H1028_EMCY_CONSUMER = 0x1028U, /**< Emergency consumer object */
|
||||
OD_H1029_ERR_BEHAVIOR = 0x1029U, /**< Error behaviour */
|
||||
OD_H1200_SDO_SERVER_1_PARAM = 0x1200U, /**< SDO server parameter */
|
||||
OD_H1280_SDO_CLIENT_1_PARAM = 0x1280U, /**< SDO client parameter */
|
||||
OD_H1300_GFC_PARAM = 0x1300U, /**< Global fail-safe command param */
|
||||
OD_H1301_SRDO_1_PARAM = 0x1301U, /**< SRDO communication parameter */
|
||||
OD_H1381_SRDO_1_MAPPING = 0x1381U, /**< SRDO mapping parameter */
|
||||
OD_H13FE_SRDO_VALID = 0x13FEU, /**< SRDO Configuration valid */
|
||||
OD_H13FF_SRDO_CHECKSUM = 0x13FFU, /**< SRDO configuration checksum */
|
||||
OD_H1400_RXPDO_1_PARAM = 0x1400U, /**< RXPDO communication parameter */
|
||||
OD_H1600_RXPDO_1_MAPPING = 0x1600U, /**< RXPDO mapping parameters */
|
||||
OD_H1800_TXPDO_1_PARAM = 0x1800U, /**< TXPDO communication parameter */
|
||||
OD_H1A00_TXPDO_1_MAPPING = 0x1A00U, /**< TXPDO mapping parameters */
|
||||
} OD_ObjDicId_30x_t;
|
||||
|
||||
/**
|
||||
* Attributes (bit masks) for OD sub-object.
|
||||
*/
|
||||
typedef enum {
|
||||
ODA_SDO_R = 0x01U, /**< SDO server may read from the variable */
|
||||
ODA_SDO_W = 0x02U, /**< SDO server may write to the variable */
|
||||
ODA_SDO_RW = 0x03U, /**< SDO server may read from or write to the variable */
|
||||
ODA_TPDO = 0x04U, /**< Variable is mappable into TPDO (can be read) */
|
||||
ODA_RPDO = 0x08U, /**< Variable is mappable into RPDO (can be written) */
|
||||
ODA_TRPDO = 0x0CU, /**< Variable is mappable into TPDO or RPDO */
|
||||
ODA_TSRDO = 0x10U, /**< Variable is mappable into transmitting SRDO */
|
||||
ODA_RSRDO = 0x20U, /**< Variable is mappable into receiving SRDO */
|
||||
ODA_TRSRDO = 0x30U, /**< Variable is mappable into tx or rx SRDO */
|
||||
ODA_MB = 0x40U, /**< Variable is multi-byte ((u)int16_t to (u)int64_t) */
|
||||
ODA_STR = 0x80U /**< Shorter value, than specified variable size, may be written to the variable. SDO write will
|
||||
fill remaining memory with zeroes. Attribute is used for VISIBLE_STRING and UNICODE_STRING. */
|
||||
} OD_attributes_t;
|
||||
|
||||
/**
|
||||
* Return codes from OD access functions.
|
||||
*
|
||||
* @ref OD_getSDOabCode() can be used to retrieve corresponding SDO abort code.
|
||||
*/
|
||||
typedef enum {
|
||||
/* !!!! WARNING !!!! If changing these values, change also OD_getSDOabCode() function! */
|
||||
ODR_PARTIAL = -1, /**< Read/write is only partial, make more calls */
|
||||
ODR_OK = 0, /**< SDO abort 0x00000000 - Read/write successfully finished */
|
||||
ODR_OUT_OF_MEM = 1, /**< SDO abort 0x05040005 - Out of memory */
|
||||
ODR_UNSUPP_ACCESS = 2, /**< SDO abort 0x06010000 - Unsupported access to an object */
|
||||
ODR_WRITEONLY = 3, /**< SDO abort 0x06010001 - Attempt to read a write only object */
|
||||
ODR_READONLY = 4, /**< SDO abort 0x06010002 - Attempt to write a read only object */
|
||||
ODR_IDX_NOT_EXIST = 5, /**< SDO abort 0x06020000 - Object does not exist in the object dict. */
|
||||
ODR_NO_MAP = 6, /**< SDO abort 0x06040041 - Object cannot be mapped to the PDO */
|
||||
ODR_MAP_LEN = 7, /**< SDO abort 0x06040042 - PDO length exceeded */
|
||||
ODR_PAR_INCOMPAT = 8, /**< SDO abort 0x06040043 - General parameter incompatibility reasons */
|
||||
ODR_DEV_INCOMPAT = 9, /**< SDO abort 0x06040047 - General internal incompatibility in device */
|
||||
ODR_HW = 10, /**< SDO abort 0x06060000 - Access failed due to hardware error */
|
||||
ODR_TYPE_MISMATCH = 11, /**< SDO abort 0x06070010 - Data type does not match */
|
||||
ODR_DATA_LONG = 12, /**< SDO abort 0x06070012 - Data type does not match, length too high */
|
||||
ODR_DATA_SHORT = 13, /**< SDO abort 0x06070013 - Data type does not match, length too short */
|
||||
ODR_SUB_NOT_EXIST = 14, /**< SDO abort 0x06090011 - Sub index does not exist */
|
||||
ODR_INVALID_VALUE = 15, /**< SDO abort 0x06090030 - Invalid value for parameter (download only) */
|
||||
ODR_VALUE_HIGH = 16, /**< SDO abort 0x06090031 - Value range of parameter written too high */
|
||||
ODR_VALUE_LOW = 17, /**< SDO abort 0x06090032 - Value range of parameter written too low */
|
||||
ODR_MAX_LESS_MIN = 18, /**< SDO abort 0x06090036 - Maximum value is less than minimum value */
|
||||
ODR_NO_RESOURCE = 19, /**< SDO abort 0x060A0023 - Resource not available: SDO connection */
|
||||
ODR_GENERAL = 20, /**< SDO abort 0x08000000 - General error */
|
||||
ODR_DATA_TRANSF = 21, /**< SDO abort 0x08000020 - Data cannot be transferred or stored to app */
|
||||
ODR_DATA_LOC_CTRL = 22, /**< SDO abort 0x08000021 - Data can't be transferred (local control) */
|
||||
ODR_DATA_DEV_STATE = 23, /**< SDO abort 0x08000022 - Data can't be transf. (present device state) */
|
||||
ODR_OD_MISSING = 24, /**< SDO abort 0x08000023 - Object dictionary not present */
|
||||
ODR_NO_DATA = 25, /**< SDO abort 0x08000024 - No data available */
|
||||
ODR_COUNT = 26 /**< Last element, number of responses */
|
||||
} ODR_t;
|
||||
|
||||
/**
|
||||
* IO stream structure, used for read/write access to OD variable, part of @ref OD_IO_t.
|
||||
*/
|
||||
typedef struct {
|
||||
void* dataOrig; /**< Pointer to original data object, defined by Object Dictionary. Default read/write functions
|
||||
* operate on it. If memory for data object is not specified by Object Dictionary, then dataOrig is
|
||||
* NULL. */
|
||||
void* object; /**< Pointer to object, passed by @ref OD_extension_init(). Can be used inside read / write functions
|
||||
* from IO extension. */
|
||||
OD_size_t dataLength; /**< Data length in bytes or 0, if length is not specified */
|
||||
OD_size_t dataOffset; /**< In case of large data, dataOffset indicates position of already transferred data */
|
||||
OD_attr_t attribute; /**< Attribute bit-field of the OD sub-object, see @ref OD_attributes_t */
|
||||
uint16_t index; /**< Index of the OD object, informative */
|
||||
uint8_t subIndex; /**< Sub index of the OD sub-object, informative */
|
||||
} OD_stream_t;
|
||||
|
||||
/**
|
||||
* Structure for input / output on the OD variable. It is initialized with @ref OD_getSub() function. Access principle
|
||||
* to OD variable is via read/write functions operating on stream, similar as standard read/write.
|
||||
*/
|
||||
typedef struct {
|
||||
/** Object Dictionary stream object, passed to read or write */
|
||||
OD_stream_t stream;
|
||||
/**
|
||||
* Function pointer for reading value from specified variable from Object Dictionary. If OD variable is larger than
|
||||
* buf, then this function must be called several times. After completed successful read function returns 'ODR_OK'.
|
||||
* If read is partial, it returns 'ODR_PARTIAL'. In case of errors function returns code similar to SDO abort code.
|
||||
*
|
||||
* Read can be restarted with @ref OD_rwRestart() function.
|
||||
*
|
||||
* At the moment, when Object Dictionary is initialized, every variable has assigned the same "read" function. This
|
||||
* default function simply copies data from Object Dictionary variable. Application can bind its own "read" function
|
||||
* for specific object. In that case application is able to calculate data for reading from own internal state at
|
||||
* the moment of "read" function call. Own "read" function on OD object can be initialized with @ref
|
||||
* OD_extension_init() function.
|
||||
*
|
||||
* "read" function must always copy all own data to buf, except if "buf" is not large enough. ("*returnCode" must
|
||||
* not return 'ODR_PARTIAL', if there is still space in "buf".)
|
||||
*
|
||||
* @warning Do not use @ref CO_LOCK_OD() and @ref CO_UNLOCK_OD() macros inside the read() function. See also @ref
|
||||
* CO_critical_sections.
|
||||
*
|
||||
* @param stream Object Dictionary stream object.
|
||||
* @param buf Pointer to external buffer, where to data will be copied.
|
||||
* @param count Size of the external buffer in bytes.
|
||||
* @param [out] countRead If return value is "ODR_OK" or "ODR_PARTIAL", then number of bytes successfully read must
|
||||
* be returned here.
|
||||
*
|
||||
* @return Value from @ref ODR_t, "ODR_OK" in case of success.
|
||||
*/
|
||||
ODR_t (*read)(OD_stream_t* stream, void* buf, OD_size_t count, OD_size_t* countRead);
|
||||
/**
|
||||
* Function pointer for writing value into specified variable inside Object Dictionary. If OD variable is larger
|
||||
* than buf, then this function must be called several times. After completed successful write function returns
|
||||
* 'ODR_OK'. If write is partial, it returns 'ODR_PARTIAL'. In case of errors function returns code similar to SDO
|
||||
* abort code.
|
||||
*
|
||||
* Write can be restarted with @ref OD_rwRestart() function.
|
||||
*
|
||||
* At the moment, when Object Dictionary is initialized, every variable has assigned the same "write" function,
|
||||
* which simply copies data to Object Dictionary variable. Application can bind its own "write" function, similar as
|
||||
* it can bind "read" function.
|
||||
*
|
||||
* "write" function must always copy all available data from buf. If OD variable expect more data, then
|
||||
* "*returnCode" must return 'ODR_PARTIAL'.
|
||||
*
|
||||
* @warning Do not use @ref CO_LOCK_OD() and @ref CO_UNLOCK_OD() macros inside the write() function. See also @ref
|
||||
* CO_critical_sections.
|
||||
*
|
||||
* @param stream Object Dictionary stream object.
|
||||
* @param buf Pointer to external buffer, from where data will be copied.
|
||||
* @param count Size of the external buffer in bytes.
|
||||
* @param [out] countWritten If return value is "ODR_OK" or "ODR_PARTIAL", then number of bytes successfully written
|
||||
* must be returned here.
|
||||
*
|
||||
* @return Value from @ref ODR_t, "ODR_OK" in case of success.
|
||||
*/
|
||||
ODR_t (*write)(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten);
|
||||
} OD_IO_t;
|
||||
|
||||
/**
|
||||
* Extension of OD object, which can optionally be specified by application in initialization phase with @ref
|
||||
* OD_extension_init() function.
|
||||
*/
|
||||
typedef struct {
|
||||
/** Object on which read and write will operate, part of @ref OD_stream_t */
|
||||
void* object;
|
||||
/** Application specified read function pointer. If NULL, then read will be disabled. @ref OD_readOriginal can be
|
||||
* used here to keep the original read function. For function description see @ref OD_IO_t. */
|
||||
ODR_t (*read)(OD_stream_t* stream, void* buf, OD_size_t count, OD_size_t* countRead);
|
||||
/** Application specified write function pointer. If NULL, then write will be disabled. @ref OD_writeOriginal can be
|
||||
* used here to keep the original write function. For function description see @ref OD_IO_t. */
|
||||
ODR_t (*write)(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten);
|
||||
#if OD_FLAGS_PDO_SIZE > 0
|
||||
/** PDO flags bit-field provides one bit for each OD variable, which exist inside OD object at specific sub index.
|
||||
* If application clears that bit, and OD variable is mapped to an event driven TPDO, then TPDO will be sent.
|
||||
*
|
||||
* @ref OD_FLAGS_PDO_SIZE can have a value from 0 to 32 bytes, which corresponds to 0 to 256 available bits. If, for
|
||||
* example, @ref OD_FLAGS_PDO_SIZE has value 4, then OD variables with sub index up to 31 will have the TPDO
|
||||
* requesting functionality. See also @ref OD_requestTPDO and @ref OD_TPDOtransmitted. */
|
||||
uint8_t flagsPDO[OD_FLAGS_PDO_SIZE];
|
||||
#endif
|
||||
} OD_extension_t;
|
||||
|
||||
/**
|
||||
* Object Dictionary entry for one OD object.
|
||||
*
|
||||
* OD entries are collected inside OD_t as array (list). Each OD entry contains basic information about OD object (index
|
||||
* and subEntriesCount), pointer to odObject with additional information about var, array or record entry and pointer to
|
||||
* extension, configurable by application.
|
||||
*/
|
||||
typedef struct {
|
||||
uint16_t index; /**< Object Dictionary index */
|
||||
uint8_t subEntriesCount; /**< Number of all sub-entries, including sub-entry at sub-index 0 */
|
||||
uint8_t odObjectType; /**< Type of the odObject, indicated by @ref OD_objectTypes_t enumerator. */
|
||||
CO_PROGMEM void* odObject; /**< OD object of type indicated by odObjectType, from which @ref OD_getSub() fetches the
|
||||
information */
|
||||
OD_extension_t* extension; /**< Extension to OD, specified by application */
|
||||
} OD_entry_t;
|
||||
|
||||
/**
|
||||
* Object Dictionary
|
||||
*/
|
||||
typedef struct {
|
||||
uint16_t size; /**< Number of elements in the list, without last element, which is blank */
|
||||
OD_entry_t* list; /**< List OD entries (table of contents), ordered by index */
|
||||
} OD_t;
|
||||
|
||||
/**
|
||||
* Read value from original OD location
|
||||
*
|
||||
* This function can be used inside read / write functions, specified by @ref OD_extension_init(). It reads data
|
||||
* directly from memory location specified by Object dictionary. If no IO extension is used on OD entry, then io->read
|
||||
* returned by @ref OD_getSub() equals to this function. See also @ref OD_IO_t.
|
||||
*/
|
||||
ODR_t OD_readOriginal(OD_stream_t* stream, void* buf, OD_size_t count, OD_size_t* countRead);
|
||||
|
||||
/**
|
||||
* Write value to original OD location
|
||||
*
|
||||
* This function can be used inside read / write functions, specified by @ref OD_extension_init(). It writes data
|
||||
* directly to memory location specified by Object dictionary. If no IO extension is used on OD entry, then io->write
|
||||
* returned by @ref OD_getSub() equals to this function. See also @ref OD_IO_t.
|
||||
*/
|
||||
ODR_t OD_writeOriginal(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten);
|
||||
|
||||
/**
|
||||
* Find OD entry in Object Dictionary
|
||||
*
|
||||
* @param od Object Dictionary
|
||||
* @param index CANopen Object Dictionary index of object in Object Dictionary
|
||||
*
|
||||
* @return Pointer to OD entry or NULL if not found
|
||||
*/
|
||||
OD_entry_t* OD_find(OD_t* od, uint16_t index);
|
||||
|
||||
/**
|
||||
* Find sub-object with specified sub-index on OD entry returned by OD_find. Function populates io structure with
|
||||
* sub-object data.
|
||||
*
|
||||
* @warning
|
||||
* Read and write functions may be called from different threads, so critical sections in custom functions must be
|
||||
* observed, see @ref CO_critical_sections.
|
||||
*
|
||||
* @param entry Object Dictionary entry.
|
||||
* @param subIndex Sub-index of the variable from the OD object.
|
||||
* @param [out] io Structure will be populated on success.
|
||||
* @param odOrig If true, then potential IO extension on entry will be ignored and access to data entry in the original
|
||||
* OD location will be returned
|
||||
*
|
||||
* @return Value from @ref ODR_t, "ODR_OK" in case of success.
|
||||
*/
|
||||
ODR_t OD_getSub(const OD_entry_t* entry, uint8_t subIndex, OD_IO_t* io, bool_t odOrig);
|
||||
|
||||
/**
|
||||
* Return index from OD entry
|
||||
*
|
||||
* @param entry Object Dictionary entry.
|
||||
*
|
||||
* @return OD index
|
||||
*/
|
||||
static inline uint16_t
|
||||
OD_getIndex(const OD_entry_t* entry) {
|
||||
return (entry != NULL) ? entry->index : 0U;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check, if OD variable is mappable to PDO or SRDO.
|
||||
*
|
||||
* If OD variable is mappable, then it may be necessary to protect read/write access from mainline function. See @ref
|
||||
* CO_critical_sections.
|
||||
*
|
||||
* @param stream Object Dictionary stream object.
|
||||
*
|
||||
* @return true, if OD variable is mappable.
|
||||
*/
|
||||
static inline bool_t
|
||||
OD_mappable(OD_stream_t* stream) {
|
||||
return (stream != NULL) ? ((stream->attribute & ((OD_attr_t)ODA_TRPDO | (OD_attr_t)ODA_TRSRDO)) != 0U) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart read or write operation on OD variable
|
||||
*
|
||||
* It is not necessary to call this function, if stream was initialized by @ref OD_getSub(). It is also not necessary to
|
||||
* call this function, if previous read or write was successfully finished.
|
||||
*
|
||||
* @param stream Object Dictionary stream object.
|
||||
*/
|
||||
static inline void
|
||||
OD_rwRestart(OD_stream_t* stream) {
|
||||
if (stream != NULL) {
|
||||
stream->dataOffset = 0U;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request TPDO, to which OD variable is mapped
|
||||
*
|
||||
* Function clears the flagPDO bit, which corresponds to OD variable at specific OD index and subindex. For this
|
||||
* functionality to work, @ref OD_extension_t must be enabled on OD variable. If OD variable is mapped to any TPDO with
|
||||
* event driven transmission, then TPDO will be transmitted after this function call. If OD variable is mapped to more
|
||||
* than one TPDO with event driven transmission, only the first matched TPDO will be transmitted.
|
||||
*
|
||||
* TPDO event driven transmission is enabled, if TPDO communication parameter, transmission type is set to 0, 254
|
||||
* or 255. For other transmission types (synchronous) flagPDO bit is ignored.
|
||||
*
|
||||
* @param entry Object Dictionary entry.
|
||||
* @param subIndex subIndex of the OD variable.
|
||||
*/
|
||||
static inline void
|
||||
OD_requestTPDO(OD_entry_t* entry, uint8_t subIndex) {
|
||||
#if OD_FLAGS_PDO_SIZE > 0
|
||||
if ((entry != NULL) && (entry->extension != NULL) && (subIndex < (OD_FLAGS_PDO_SIZE * 8U))) {
|
||||
/* clear subIndex-th bit */
|
||||
uint8_t mask = ~(1U << (subIndex & 0x07U));
|
||||
entry->extension->flagsPDO[subIndex >> 3] &= mask;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if requested TPDO was transmitted
|
||||
*
|
||||
* @param entry Object Dictionary entry.
|
||||
* @param subIndex subIndex of the OD variable.
|
||||
*
|
||||
* @return Return true if event driven TPDO with mapping to OD variable, indicated by entry and subIndex, was
|
||||
* transmitted since last @ref OD_requestTPDO call. If there was no @ref OD_requestTPDO call yet and TPDO was
|
||||
* transmitted by other event, function also returns true.
|
||||
*/
|
||||
static inline bool_t
|
||||
OD_TPDOtransmitted(OD_entry_t* entry, uint8_t subIndex) {
|
||||
#if OD_FLAGS_PDO_SIZE > 0
|
||||
if ((entry != NULL) && (entry->extension != NULL) && (subIndex < (OD_FLAGS_PDO_SIZE * 8U))) {
|
||||
/* return true, if subIndex-th bit is set */
|
||||
uint8_t mask = 1U << (subIndex & 0x07U);
|
||||
if ((entry->extension->flagsPDO[subIndex >> 3] & mask) != 0U) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SDO abort code from returnCode
|
||||
*
|
||||
* @param returnCode Returned from some OD access functions
|
||||
*
|
||||
* @return Corresponding @ref CO_SDO_abortCode_t
|
||||
*/
|
||||
uint32_t OD_getSDOabCode(ODR_t returnCode);
|
||||
|
||||
/**
|
||||
* Extend OD object with own read/write functions and/or flagsPDO
|
||||
*
|
||||
* This function gives application very powerful tool: definition of own IO access on OD object. Structure and
|
||||
* attributes are the same as defined in original OD object, but data are read directly from (or written directly to)
|
||||
* application specified object via custom function calls.
|
||||
*
|
||||
* Before this function specifies extension, OD variables are accessed from original OD location. After this function
|
||||
* specifies extension OD variables are accessed from read/write functions specified by extension. (Except when "odOrig"
|
||||
* argument to @ref OD_getSub() is set to true.)
|
||||
*
|
||||
* This function must also be used, when flagsPDO needs to be enabled for specific entry.
|
||||
*
|
||||
* @warning
|
||||
* Object dictionary storage works only directly on OD variables. It does not access read function specified here. So,
|
||||
* if extended OD objects needs to be preserved, then @ref OD_writeOriginal can be used inside custom write function.
|
||||
*
|
||||
* @warning
|
||||
* Read and write functions may be called from different threads, so critical sections in custom functions must be
|
||||
* observed, see @ref CO_critical_sections.
|
||||
*
|
||||
* @param entry Object Dictionary entry.
|
||||
* @param extension Extension object, which must be initialized externally. Extension object must exist permanently. If
|
||||
* NULL, extension will be removed.
|
||||
*
|
||||
* @return "ODR_OK" on success, "ODR_IDX_NOT_EXIST" if OD object doesn't exist.
|
||||
*/
|
||||
static inline ODR_t
|
||||
OD_extension_init(OD_entry_t* entry, OD_extension_t* extension) {
|
||||
if (entry == NULL) {
|
||||
return ODR_IDX_NOT_EXIST;
|
||||
}
|
||||
entry->extension = extension;
|
||||
return ODR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @defgroup CO_ODgetSetters Getters and setters
|
||||
* @{
|
||||
*
|
||||
* Getter and setter helper functions for accessing different types of Object Dictionary variables.
|
||||
*/
|
||||
/**
|
||||
* Get variable from Object Dictionary
|
||||
*
|
||||
* @param entry Object Dictionary entry.
|
||||
* @param subIndex Sub-index of the variable from the OD object.
|
||||
* @param [out] val Value will be written here.
|
||||
* @param len Size of value to retrieve from OD.
|
||||
* @param odOrig If true, then potential IO extension on entry will be ignored and data in the original OD location will
|
||||
* be returned.
|
||||
*
|
||||
* @return Value from @ref ODR_t, "ODR_OK" in case of success. Error, if variable does not exist in object dictionary or
|
||||
* it does not have the correct length or other reason.
|
||||
*/
|
||||
ODR_t OD_get_value(const OD_entry_t* entry, uint8_t subIndex, void* val, OD_size_t len, bool_t odOrig);
|
||||
|
||||
/** Get int8_t variable from Object Dictionary, see @ref OD_get_value */
|
||||
static inline ODR_t
|
||||
OD_get_i8(const OD_entry_t* entry, uint8_t subIndex, int8_t* val, bool_t odOrig) {
|
||||
return OD_get_value(entry, subIndex, val, sizeof(*val), odOrig);
|
||||
}
|
||||
|
||||
/** Get int16_t variable from Object Dictionary, see @ref OD_get_value */
|
||||
static inline ODR_t
|
||||
OD_get_i16(const OD_entry_t* entry, uint8_t subIndex, int16_t* val, bool_t odOrig) {
|
||||
return OD_get_value(entry, subIndex, val, sizeof(*val), odOrig);
|
||||
}
|
||||
|
||||
/** Get int32_t variable from Object Dictionary, see @ref OD_get_value */
|
||||
static inline ODR_t
|
||||
OD_get_i32(const OD_entry_t* entry, uint8_t subIndex, int32_t* val, bool_t odOrig) {
|
||||
return OD_get_value(entry, subIndex, val, sizeof(*val), odOrig);
|
||||
}
|
||||
|
||||
/** Get int64_t variable from Object Dictionary, see @ref OD_get_value */
|
||||
static inline ODR_t
|
||||
OD_get_i64(const OD_entry_t* entry, uint8_t subIndex, int64_t* val, bool_t odOrig) {
|
||||
return OD_get_value(entry, subIndex, val, sizeof(*val), odOrig);
|
||||
}
|
||||
|
||||
/** Get uint8_t variable from Object Dictionary, see @ref OD_get_value */
|
||||
static inline ODR_t
|
||||
OD_get_u8(const OD_entry_t* entry, uint8_t subIndex, uint8_t* val, bool_t odOrig) {
|
||||
return OD_get_value(entry, subIndex, val, sizeof(*val), odOrig);
|
||||
}
|
||||
|
||||
/** Get uint16_t variable from Object Dictionary, see @ref OD_get_value */
|
||||
static inline ODR_t
|
||||
OD_get_u16(const OD_entry_t* entry, uint8_t subIndex, uint16_t* val, bool_t odOrig) {
|
||||
return OD_get_value(entry, subIndex, val, sizeof(*val), odOrig);
|
||||
}
|
||||
|
||||
/** Get uint32_t variable from Object Dictionary, see @ref OD_get_value */
|
||||
static inline ODR_t
|
||||
OD_get_u32(const OD_entry_t* entry, uint8_t subIndex, uint32_t* val, bool_t odOrig) {
|
||||
return OD_get_value(entry, subIndex, val, sizeof(*val), odOrig);
|
||||
}
|
||||
|
||||
/** Get uint64_t variable from Object Dictionary, see @ref OD_get_value */
|
||||
static inline ODR_t
|
||||
OD_get_u64(const OD_entry_t* entry, uint8_t subIndex, uint64_t* val, bool_t odOrig) {
|
||||
return OD_get_value(entry, subIndex, val, sizeof(*val), odOrig);
|
||||
}
|
||||
|
||||
/** Get float32_t variable from Object Dictionary, see @ref OD_get_value */
|
||||
static inline ODR_t
|
||||
OD_get_f32(const OD_entry_t* entry, uint8_t subIndex, float32_t* val, bool_t odOrig) {
|
||||
return OD_get_value(entry, subIndex, val, sizeof(*val), odOrig);
|
||||
}
|
||||
|
||||
/** Get float64_t variable from Object Dictionary, see @ref OD_get_value */
|
||||
static inline ODR_t
|
||||
OD_get_f64(const OD_entry_t* entry, uint8_t subIndex, float64_t* val, bool_t odOrig) {
|
||||
return OD_get_value(entry, subIndex, val, sizeof(*val), odOrig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set variable in Object Dictionary
|
||||
*
|
||||
* @param entry Object Dictionary entry.
|
||||
* @param subIndex Sub-index of the variable from the OD object.
|
||||
* @param val Pointer to value to write.
|
||||
* @param len Size of value to write.
|
||||
* @param odOrig If true, then potential IO extension on entry will be ignored and data in the original OD location will
|
||||
* be written.
|
||||
*
|
||||
* @return Value from @ref ODR_t, "ODR_OK" in case of success. Error, if variable does not exist in object dictionary or
|
||||
* it does not have the correct length or other reason.
|
||||
*/
|
||||
ODR_t OD_set_value(const OD_entry_t* entry, uint8_t subIndex, void* val, OD_size_t len, bool_t odOrig);
|
||||
|
||||
/** Set int8_t variable in Object Dictionary, see @ref OD_set_value */
|
||||
static inline ODR_t
|
||||
OD_set_i8(const OD_entry_t* entry, uint8_t subIndex, int8_t val, bool_t odOrig) {
|
||||
return OD_set_value(entry, subIndex, &val, sizeof(val), odOrig);
|
||||
}
|
||||
|
||||
/** Set int16_t variable in Object Dictionary, see @ref OD_set_value */
|
||||
static inline ODR_t
|
||||
OD_set_i16(const OD_entry_t* entry, uint8_t subIndex, int16_t val, bool_t odOrig) {
|
||||
return OD_set_value(entry, subIndex, &val, sizeof(val), odOrig);
|
||||
}
|
||||
|
||||
/** Set int32_t variable in Object Dictionary, see @ref OD_set_value */
|
||||
static inline ODR_t
|
||||
OD_set_i32(const OD_entry_t* entry, uint8_t subIndex, int32_t val, bool_t odOrig) {
|
||||
return OD_set_value(entry, subIndex, &val, sizeof(val), odOrig);
|
||||
}
|
||||
|
||||
/** Set int32_t variable in Object Dictionary, see @ref OD_set_value */
|
||||
static inline ODR_t
|
||||
OD_set_i64(const OD_entry_t* entry, uint8_t subIndex, int64_t val, bool_t odOrig) {
|
||||
return OD_set_value(entry, subIndex, &val, sizeof(val), odOrig);
|
||||
}
|
||||
|
||||
/** Set uint8_t variable in Object Dictionary, see @ref OD_set_value */
|
||||
static inline ODR_t
|
||||
OD_set_u8(const OD_entry_t* entry, uint8_t subIndex, uint8_t val, bool_t odOrig) {
|
||||
return OD_set_value(entry, subIndex, &val, sizeof(val), odOrig);
|
||||
}
|
||||
|
||||
/** Set uint16_t variable in Object Dictionary, see @ref OD_set_value */
|
||||
static inline ODR_t
|
||||
OD_set_u16(const OD_entry_t* entry, uint8_t subIndex, uint16_t val, bool_t odOrig) {
|
||||
return OD_set_value(entry, subIndex, &val, sizeof(val), odOrig);
|
||||
}
|
||||
|
||||
/** Set uint32_t variable in Object Dictionary, see @ref OD_set_value */
|
||||
static inline ODR_t
|
||||
OD_set_u32(const OD_entry_t* entry, uint8_t subIndex, uint32_t val, bool_t odOrig) {
|
||||
return OD_set_value(entry, subIndex, &val, sizeof(val), odOrig);
|
||||
}
|
||||
|
||||
/** Set uint64_t variable in Object Dictionary, see @ref OD_set_value */
|
||||
static inline ODR_t
|
||||
OD_set_u64(const OD_entry_t* entry, uint8_t subIndex, uint64_t val, bool_t odOrig) {
|
||||
return OD_set_value(entry, subIndex, &val, sizeof(val), odOrig);
|
||||
}
|
||||
|
||||
/** Set float32_t variable in Object Dictionary, see @ref OD_set_value */
|
||||
static inline ODR_t
|
||||
OD_set_f32(const OD_entry_t* entry, uint8_t subIndex, float32_t val, bool_t odOrig) {
|
||||
return OD_set_value(entry, subIndex, &val, sizeof(val), odOrig);
|
||||
}
|
||||
|
||||
/** Set float64_t variable in Object Dictionary, see @ref OD_set_value */
|
||||
static inline ODR_t
|
||||
OD_set_f64(const OD_entry_t* entry, uint8_t subIndex, float64_t val, bool_t odOrig) {
|
||||
return OD_set_value(entry, subIndex, &val, sizeof(val), odOrig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pointer to memory which holds data variable from Object Dictionary
|
||||
*
|
||||
* Function always returns "dataOrig" pointer, which points to data in the original OD location. Take care, if IO
|
||||
* extension is enabled on OD entry. Take also care that "dataOrig" could be not aligned to data type.
|
||||
*
|
||||
* @param entry Object Dictionary entry.
|
||||
* @param subIndex Sub-index of the variable from the OD object.
|
||||
* @param len Required length of the variable. If len is different than zero, then actual length of the variable must
|
||||
* match len or error is returned.
|
||||
* @param [out] err Error reason is written here in case of error (allow NULL).
|
||||
*
|
||||
* @return Pointer to variable in Object Dictionary or NULL in case of error.
|
||||
*/
|
||||
void* OD_getPtr(const OD_entry_t* entry, uint8_t subIndex, OD_size_t len, ODR_t* err);
|
||||
/** @} */ /* CO_ODgetSetters */
|
||||
|
||||
#if defined OD_DEFINITION || defined CO_DOXYGEN
|
||||
/**
|
||||
* @defgroup CO_ODdefinition OD definition objects
|
||||
* @{
|
||||
*
|
||||
* Types and functions used only for definition of Object Dictionary
|
||||
*/
|
||||
/**
|
||||
* Types for OD object.
|
||||
*/
|
||||
typedef enum {
|
||||
ODT_VAR = 0x01, /**< This type corresponds to CANopen Object Dictionary object with object code equal to VAR. OD
|
||||
object is type of @ref OD_obj_var_t and represents single variable of any type (any length),
|
||||
located on sub-index 0. Other sub-indexes are not used. */
|
||||
ODT_ARR = 0x02, /**< This type corresponds to CANopen Object Dictionary object with object code equal to ARRAY. OD
|
||||
object is type of @ref OD_obj_array_t and represents array of variables with the same type,
|
||||
located on sub-indexes above 0. Sub-index 0 is of type uint8_t and usually represents length of
|
||||
the array. */
|
||||
ODT_REC = 0x03, /**< This type corresponds to CANopen Object Dictionary object with object code equal to RECORD.
|
||||
This type of OD object represents structure of the variables. Each variable from the structure
|
||||
can have own type and own attribute. OD object is an array of elements of type @ref OD_obj_var_t.
|
||||
Variable at sub-index 0 is of type uint8_t and usually represents number of sub-elements in the
|
||||
structure. */
|
||||
ODT_TYPE_MASK = 0x0F, /**< Mask for basic type */
|
||||
} OD_objectTypes_t;
|
||||
|
||||
/**
|
||||
* Object for single OD variable, used for "VAR" type OD objects
|
||||
*/
|
||||
typedef struct {
|
||||
void* dataOrig; /**< Pointer to data */
|
||||
OD_attr_t attribute; /**< Attribute bitfield, see @ref OD_attributes_t */
|
||||
OD_size_t dataLength; /**< Data length in bytes */
|
||||
} OD_obj_var_t;
|
||||
|
||||
/**
|
||||
* Object for OD array of variables, used for "ARRAY" type OD objects
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t* dataOrig0; /**< Pointer to data for sub-index 0 */
|
||||
void* dataOrig; /**< Pointer to array of data */
|
||||
OD_attr_t attribute0; /**< Attribute bitfield for sub-index 0, see @ref OD_attributes_t */
|
||||
OD_attr_t attribute; /**< Attribute bitfield for array elements */
|
||||
OD_size_t dataElementLength; /**< Data length of array elements in bytes */
|
||||
OD_size_t dataElementSizeof; /**< Sizeof one array element in bytes */
|
||||
} OD_obj_array_t;
|
||||
|
||||
/**
|
||||
* Object for OD sub-elements, used in "RECORD" type OD objects
|
||||
*/
|
||||
typedef struct {
|
||||
void* dataOrig; /**< Pointer to data */
|
||||
uint8_t subIndex; /**< Sub index of element. */
|
||||
OD_attr_t attribute; /**< Attribute bitfield, see @ref OD_attributes_t */
|
||||
OD_size_t dataLength; /**< Data length in bytes */
|
||||
} OD_obj_record_t;
|
||||
|
||||
/** @} */ /* CO_ODdefinition */
|
||||
|
||||
#endif /* defined OD_DEFINITION */
|
||||
|
||||
/** @} */ /* CO_ODinterface */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CO_OD_INTERFACE_H */
|
||||
1472
Middleware/CANopenNode/301/CO_PDO.c
Normal file
1472
Middleware/CANopenNode/301/CO_PDO.c
Normal file
File diff suppressed because it is too large
Load Diff
386
Middleware/CANopenNode/301/CO_PDO.h
Normal file
386
Middleware/CANopenNode/301/CO_PDO.h
Normal file
@@ -0,0 +1,386 @@
|
||||
/**
|
||||
* CANopen Process Data Object protocol.
|
||||
*
|
||||
* @file CO_PDO.h
|
||||
* @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.
|
||||
*/
|
||||
|
||||
#ifndef CO_PDO_H
|
||||
#define CO_PDO_H
|
||||
|
||||
#include "301/CO_ODinterface.h"
|
||||
#include "301/CO_Emergency.h"
|
||||
#include "301/CO_SYNC.h"
|
||||
|
||||
/* default configuration, see CO_config.h */
|
||||
#ifndef CO_CONFIG_PDO
|
||||
#define CO_CONFIG_PDO \
|
||||
(CO_CONFIG_RPDO_ENABLE | CO_CONFIG_TPDO_ENABLE | CO_CONFIG_RPDO_TIMERS_ENABLE | CO_CONFIG_TPDO_TIMERS_ENABLE \
|
||||
| CO_CONFIG_PDO_SYNC_ENABLE | CO_CONFIG_PDO_OD_IO_ACCESS | CO_CONFIG_GLOBAL_RT_FLAG_CALLBACK_PRE \
|
||||
| CO_CONFIG_GLOBAL_FLAG_TIMERNEXT | CO_CONFIG_GLOBAL_FLAG_OD_DYNAMIC)
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_PDO) & (CO_CONFIG_RPDO_ENABLE | CO_CONFIG_TPDO_ENABLE)) != 0) || defined CO_DOXYGEN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_PDO PDO
|
||||
* CANopen Process Data Object protocol.
|
||||
*
|
||||
* @ingroup CO_CANopen_301
|
||||
* @{
|
||||
* Process data objects are used for real-time data transfer with no protocol overhead.
|
||||
*
|
||||
* TPDO with specific identifier is transmitted by one device and recieved by zero or more devices as RPDO. PDO
|
||||
* communication parameters(COB-ID, transmission type, etc.) are in the Object Dictionary at index 0x1400+ and 0x1800+.
|
||||
* PDO mapping parameters (size and contents of the PDO) are in the Object Dictionary at index 0x1600+ and 0x1A00+.
|
||||
*
|
||||
* Features of the PDO as implemented in CANopenNode:
|
||||
* - Dynamic PDO mapping.
|
||||
* - Map granularity of one byte.
|
||||
* - Data from OD variables are accessed via @ref OD_IO_t read()/write() functions, which gives a great usefulness to
|
||||
* the application.
|
||||
* - For systems with very low memory and processing capabilities there is a simplified @ref CO_CONFIG_PDO option,
|
||||
* where instead of read()/write() access, PDO data are copied directly to/from memory locations of OD variables.
|
||||
* - After RPDO is received from CAN bus, its data are copied to internal buffer (inside fast CAN receive interrupt).
|
||||
* Function CO_RPDO_process() (called by application) copies data to the mapped objects in the Object Dictionary.
|
||||
* Synchronous RPDOs are processed AFTER reception of the next SYNC message.
|
||||
* - Function CO_TPDO_process() (called by application) sends TPDO when necessary. There are different transmission
|
||||
* types possible, controlled by: SYNC message, event timer, @ref CO_TPDOsendRequest() by application or @ref
|
||||
* OD_requestTPDO(), where application can request TPDO for OD variable mapped to any of them. In later case
|
||||
* application may, for example, monitor change of state of the OD variable and indicate TPDO request on it.
|
||||
*
|
||||
* @anchor CO_PDO_CAN_ID
|
||||
* ### CAN identifiers for PDO
|
||||
*
|
||||
* Each PDO can be configured with any valid 11-bit CAN identifier. Lower numbers have higher priorities on CAN bus. As
|
||||
* a general rule, each CAN message is identified with own CAN-ID, which must be unique and produced by single source.
|
||||
* The same is with PDO objects: Any TPDO produced on the CANopen network must have unique CAN-ID and there can be zero
|
||||
* to many RPDOs (from different devices) configured to match the CAN-ID of the TPDO of interest.
|
||||
*
|
||||
* CANopen standard provides pre-defined connection sets for four RPDOs and four TPDOs on each device with specific
|
||||
* 7-bit Node-ID. These are default values and are usable in configuration, where CANopen network contains a master
|
||||
* device, which directly communicates with many slaves. In de-centralized systems, where devices operate without a
|
||||
* master, it makes sense to configure CAN-IDs of the RPDOs to the non-default values.
|
||||
*
|
||||
* Default CAN identifiers for first four TPDOs on device with specific CANopen Node-Id are: 0x180+NodeId, 0x280+NodeId,
|
||||
* 0x380+NodeId and 0x480+NodeId.
|
||||
*
|
||||
* Default CAN identifiers for first four RPDOs on device with specific CANopen Node-Id are: 0x200+NodeId, 0x300+NodeId,
|
||||
* 0x400+NodeId and 0x500+NodeId.
|
||||
*
|
||||
* CANopenNode handles default (pre-defined) CAN-IDs. If it is detected, that PDO is configured with default CAN-ID
|
||||
* (when writing to OD variable PDO communication parameter, COB-ID), then COB-ID is stored without Node-Id to the
|
||||
* Object Dictionary. If Node-ID is changed, then COB-ID will always contain correct default CAN-ID (default CAN-ID +
|
||||
* Node-ID). If PDO is configured with non-default CAN-ID, then it will be stored to the Object Dictionary as is.
|
||||
*
|
||||
* If configuration CO_CONFIG_FLAG_OD_DYNAMIC is enabled in @ref CO_CONFIG_PDO, then PDOs can be configured dynamically,
|
||||
* also in NMT operational state. Otherwise PDOs are configured only in reset communication section and also default
|
||||
* CAN-IDs are always stored to OD as is, no default node-id is handled.
|
||||
*
|
||||
* Configure PDO by writing to the OD variables in the following procedure:
|
||||
* - Disable the PDO by setting bit-31 to 1 in PDO communication parameter, COB-ID
|
||||
* - Node-Id can be configured only when PDO is disabled.
|
||||
* - Disable mapping by setting PDO mapping parameter, sub index 0 to 0
|
||||
* - Configure mapping
|
||||
* - Enable mapping by setting PDO mapping param, sub 0 to number of mapped objects
|
||||
* - Enable the PDO by setting bit-31 to 0 in PDO communication parameter, COB-ID
|
||||
*/
|
||||
|
||||
/** Maximum size of PDO message, 8 for standard CAN */
|
||||
#ifndef CO_PDO_MAX_SIZE
|
||||
#define CO_PDO_MAX_SIZE 8U
|
||||
#endif
|
||||
|
||||
/** Maximum number of entries, which can be mapped to PDO, 8 for standard CAN, may be less to preserve RAM usage */
|
||||
#ifndef CO_PDO_MAX_MAPPED_ENTRIES
|
||||
#define CO_PDO_MAX_MAPPED_ENTRIES 8U
|
||||
#endif
|
||||
|
||||
/** Number of CANopen RPDO objects, which uses default CAN indentifiers. By default first four RPDOs have pre-defined
|
||||
* CAN identifiers, which depends on node-id. This constant may be set to 0 to disable functionality or set to any other
|
||||
* value. For example, if there are several logical devices inside single CANopen device, then more than four RPDOs may
|
||||
* have pre-defined CAN identifiers. In that case RPDO5 has CAN_ID=0x200+NodeId+1, RPDO6 has CAN_ID=0x300+NodeId+1,
|
||||
* RPDO9 has CAN_ID=0x200+NodeId+2 and so on. */
|
||||
#ifndef CO_RPDO_DEFAULT_CANID_COUNT
|
||||
#define CO_RPDO_DEFAULT_CANID_COUNT 4U
|
||||
#endif
|
||||
|
||||
/** Number of CANopen TPDO objects, which uses default CAN indentifiers. If value is more than four, then pre-defined
|
||||
* pre-defined CAN identifiers are: TPDO5 has CAN_ID=0x180+NodeId+1, TPDO6 has CAN_ID=0x280+NodeId+1, TPDO9 has
|
||||
* CAN_ID=0x180+NodeId+2 and so on. For description see @ref CO_RPDO_DEFAULT_CANID_COUNT. */
|
||||
#ifndef CO_TPDO_DEFAULT_CANID_COUNT
|
||||
#define CO_TPDO_DEFAULT_CANID_COUNT 4U
|
||||
#endif
|
||||
|
||||
#ifndef CO_PDO_OWN_TYPES
|
||||
/** Variable of type CO_PDO_size_t contains data length in bytes of PDO */
|
||||
typedef uint8_t CO_PDO_size_t;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* PDO transmission Types
|
||||
*/
|
||||
typedef enum {
|
||||
CO_PDO_TRANSM_TYPE_SYNC_ACYCLIC = 0U, /**< synchronous (acyclic) */
|
||||
CO_PDO_TRANSM_TYPE_SYNC_1 = 1U, /**< synchronous (cyclic every sync) */
|
||||
CO_PDO_TRANSM_TYPE_SYNC_240 = 0xF0U, /**< synchronous (cyclic every 240-th sync) */
|
||||
CO_PDO_TRANSM_TYPE_SYNC_EVENT_LO = 0xFEU, /**< event-driven, lower value (manufacturer specific), */
|
||||
CO_PDO_TRANSM_TYPE_SYNC_EVENT_HI = 0xFFU /**< event-driven, higher value (device profile and application profile
|
||||
specific) */
|
||||
} CO_PDO_transmissionTypes_t;
|
||||
|
||||
/**
|
||||
* PDO object, common properties
|
||||
*/
|
||||
typedef struct {
|
||||
CO_EM_t* em; /**< From CO_xPDO_init() */
|
||||
CO_CANmodule_t* CANdev; /**< From CO_xPDO_init() */
|
||||
bool_t valid; /**< True, if PDO is enabled and valid */
|
||||
CO_PDO_size_t dataLength; /**< Data length of the received PDO message. Calculated from mapping */
|
||||
uint8_t mappedObjectsCount; /**< Number of mapped objects in PDO */
|
||||
#if (((CO_CONFIG_PDO)&CO_CONFIG_PDO_OD_IO_ACCESS) != 0) || defined CO_DOXYGEN
|
||||
OD_IO_t OD_IO[CO_PDO_MAX_MAPPED_ENTRIES]; /**< Object dictionary interface for all mapped entries. OD_IO.dataOffset
|
||||
has special usage with PDO. It stores information about the number of bytes
|
||||
mapped to the PDO. If CO_CONFIG_PDO_BITWISE_MAPPING is enabled, it stores a
|
||||
number of bits instead. In either case, the number of bits mapped to the PDO
|
||||
can not be more than the size of the variable (OD_IO.dataLength) in bits.
|
||||
mappedLengthBits can be less or equal to the OD_IO.dataLength*8.
|
||||
mappedLengthBits greater than OD_IO.dataLength*8 indicates
|
||||
erroneous mapping. OD_IO.dataOffset is set to 0 before read/write
|
||||
function call and after the call OD_IO.dataOffset is set back to
|
||||
mappedLength (if CO_CONFIG_PDO_BITWISE_MAPPING is disabled) or
|
||||
mappedLengthBits (if CO_CONFIG_PDO_BITWISE_MAPPING is enabled). */
|
||||
#if OD_FLAGS_PDO_SIZE > 0
|
||||
uint8_t* flagPDObyte[CO_PDO_MAX_MAPPED_ENTRIES]; /**< Pointer to byte, which contains PDO flag bit from @ref
|
||||
OD_extension_t */
|
||||
uint8_t flagPDObitmask[CO_PDO_MAX_MAPPED_ENTRIES]; /**< Bitmask for the flagPDObyte */
|
||||
#endif
|
||||
#else
|
||||
/* Pointers to data objects inside OD, where PDO will be copied */
|
||||
uint8_t* mapPointer[CO_PDO_MAX_SIZE];
|
||||
#if OD_FLAGS_PDO_SIZE > 0
|
||||
uint8_t* flagPDObyte[CO_PDO_MAX_SIZE];
|
||||
uint8_t flagPDObitmask[CO_PDO_MAX_SIZE];
|
||||
#endif
|
||||
#endif
|
||||
#if (((CO_CONFIG_PDO)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0) || defined CO_DOXYGEN
|
||||
bool_t isRPDO; /**< True for RPDO, false for TPDO */
|
||||
OD_t* OD; /**< From CO_xPDO_init() */
|
||||
uint16_t CANdevIdx; /**< From CO_xPDO_init() */
|
||||
uint16_t preDefinedCanId; /**< From CO_xPDO_init() */
|
||||
uint16_t configuredCanId; /**< Currently configured CAN identifier */
|
||||
OD_extension_t OD_communicationParam_ext; /**< Extension for OD object */
|
||||
OD_extension_t OD_mappingParam_extension; /**< Extension for OD object */
|
||||
#endif
|
||||
} CO_PDO_common_t;
|
||||
|
||||
/*******************************************************************************
|
||||
* R P D O
|
||||
******************************************************************************/
|
||||
#if (((CO_CONFIG_PDO)&CO_CONFIG_RPDO_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Number of buffers for received CAN message for RPDO
|
||||
*/
|
||||
#if (((CO_CONFIG_PDO)&CO_CONFIG_PDO_SYNC_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
#define CO_RPDO_CAN_BUFFERS_COUNT 2
|
||||
#else
|
||||
#define CO_RPDO_CAN_BUFFERS_COUNT 1
|
||||
#endif
|
||||
|
||||
/**
|
||||
* RPDO object.
|
||||
*/
|
||||
typedef struct {
|
||||
CO_PDO_common_t PDO_common; /**< PDO common properties, must be first element in this object */
|
||||
volatile void* CANrxNew[CO_RPDO_CAN_BUFFERS_COUNT]; /**< Variable indicates, if new PDO message received from CAN */
|
||||
uint8_t CANrxData[CO_RPDO_CAN_BUFFERS_COUNT][CO_PDO_MAX_SIZE]; /**< CO_PDO_MAX_SIZE data bytes of the received
|
||||
message. */
|
||||
uint8_t receiveError; /**< Indication of RPDO length errors, use with CO_PDO_receiveErrors_t */
|
||||
#if (((CO_CONFIG_PDO)&CO_CONFIG_PDO_SYNC_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
CO_SYNC_t* SYNC; /**< From CO_RPDO_init() */
|
||||
bool_t synchronous; /**< True if transmissionType <= 240 */
|
||||
#endif
|
||||
#if (((CO_CONFIG_PDO)&CO_CONFIG_RPDO_TIMERS_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
uint32_t timeoutTime_us; /**< Maximum timeout time between received PDOs in microseconds. Configurable by OD
|
||||
variable RPDO communication parameter, event-timer. */
|
||||
uint32_t timeoutTimer; /**< Timeout timer variable in microseconds */
|
||||
#endif
|
||||
#if (((CO_CONFIG_PDO)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
void (*pFunctSignalPre)(void* object); /**< From CO_RPDO_initCallbackPre() or NULL */
|
||||
void* functSignalObjectPre; /**< From CO_RPDO_initCallbackPre() or NULL */
|
||||
#endif
|
||||
} CO_RPDO_t;
|
||||
|
||||
/**
|
||||
* Initialize RPDO object.
|
||||
*
|
||||
* Function must be called in the end of the communication reset section, after all application initialization.
|
||||
* Otherwise mapping to application OD variables will not be correct.
|
||||
*
|
||||
* @param RPDO This object will be initialized.
|
||||
* @param OD Object Dictionary.
|
||||
* @param em Emergency object.
|
||||
* @param SYNC SYNC object, may be NULL.
|
||||
* @param preDefinedCanId CAN identifier from pre-defined connection set, including node-id for first four PDOs, or 0
|
||||
* otherwise, see @ref CO_PDO_CAN_ID
|
||||
* @param OD_14xx_RPDOCommPar OD entry for 0x1400+ - "RPDO communication parameter", entry is required.
|
||||
* @param OD_16xx_RPDOMapPar OD entry for 0x1600+ - "RPDO mapping parameter", entry is required.
|
||||
* @param CANdevRx CAN device for PDO reception.
|
||||
* @param CANdevRxIdx Index of receive buffer in the above CAN device.
|
||||
* @param [out] errInfo Additional information in case of error, may be NULL.
|
||||
*
|
||||
* @return #CO_ReturnError_t CO_ERROR_NO on success.
|
||||
*/
|
||||
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) || defined CO_DOXYGEN
|
||||
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);
|
||||
|
||||
#if (((CO_CONFIG_PDO)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Initialize RPDO callback function.
|
||||
*
|
||||
* Function initializes optional callback function, which should immediately start processing of CO_RPDO_process()
|
||||
* function. Callback is called after RPDO message is received from the CAN bus.
|
||||
*
|
||||
* @param RPDO This object.
|
||||
* @param object Pointer to object, which will be passed to pFunctSignalPre().
|
||||
* @param pFunctSignalPre Pointer to the callback function. Not called if NULL.
|
||||
*/
|
||||
void CO_RPDO_initCallbackPre(CO_RPDO_t* RPDO, void* object, void (*pFunctSignalPre)(void* object));
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Process received PDO messages.
|
||||
*
|
||||
* Function must be called cyclically in any NMT state. It copies data from RPDO to Object Dictionary variables if: new
|
||||
* PDO receives and PDO is valid and NMT operating state is operational. Synchronous RPDOs are processed after next SYNC
|
||||
* message.
|
||||
*
|
||||
* @param RPDO This object.
|
||||
* @param timeDifference_us Time difference from previous function call.
|
||||
* @param [out] timerNext_us info to OS - see CO_process().
|
||||
* @param NMTisOperational True if this node is in NMT_OPERATIONAL state.
|
||||
* @param syncWas True, if CANopen SYNC message was just received or transmitted.
|
||||
*/
|
||||
void CO_RPDO_process(CO_RPDO_t* RPDO,
|
||||
#if (((CO_CONFIG_PDO)&CO_CONFIG_RPDO_TIMERS_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
uint32_t timeDifference_us, uint32_t* timerNext_us,
|
||||
#endif
|
||||
bool_t NMTisOperational, bool_t syncWas);
|
||||
#endif /* (CO_CONFIG_PDO) & CO_CONFIG_RPDO_ENABLE */
|
||||
|
||||
/*******************************************************************************
|
||||
* T P D O
|
||||
******************************************************************************/
|
||||
#if (((CO_CONFIG_PDO)&CO_CONFIG_TPDO_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* TPDO object.
|
||||
*/
|
||||
typedef struct {
|
||||
CO_PDO_common_t PDO_common; /**< PDO common properties, must be first element in this object */
|
||||
CO_CANtx_t* CANtxBuff; /**< CAN transmit buffer inside CANdev */
|
||||
uint8_t transmissionType; /**< Copy of the variable from object dictionary */
|
||||
bool_t sendRequest; /**< If this flag is set and TPDO is event driven (transmission type is 0, 254 or 255),
|
||||
then PDO will be sent by CO_TPDO_process(). */
|
||||
#if (((CO_CONFIG_PDO)&CO_CONFIG_PDO_SYNC_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
CO_SYNC_t* SYNC; /**< From CO_TPDO_init() */
|
||||
uint8_t syncStartValue; /**< Copy of the variable from object dictionary */
|
||||
uint8_t syncCounter; /**< SYNC counter used for PDO sending */
|
||||
#endif
|
||||
#if (((CO_CONFIG_PDO)&CO_CONFIG_TPDO_TIMERS_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
uint32_t inhibitTime_us; /**< Inhibit time from object dictionary translated to microseconds */
|
||||
uint32_t eventTime_us; /**< Event time from object dictionary translated to microseconds */
|
||||
uint32_t inhibitTimer; /**< Inhibit timer variable in microseconds */
|
||||
uint32_t eventTimer; /**< Event timer variable in microseconds */
|
||||
#endif
|
||||
} CO_TPDO_t;
|
||||
|
||||
/**
|
||||
* Initialize TPDO object.
|
||||
*
|
||||
* Function must be called in the end of the communication reset section, after all application initialization.
|
||||
* Otherwise mapping to application OD variables will not be correct.
|
||||
*
|
||||
* @param TPDO This object will be initialized.
|
||||
* @param OD Object Dictionary.
|
||||
* @param em Emergency object.
|
||||
* @param SYNC SYNC object, may be NULL.
|
||||
* @param preDefinedCanId CAN identifier from pre-defined connection set, including node-id for first four PDOs, or 0
|
||||
* otherwise, see @ref CO_PDO_CAN_ID
|
||||
* @param OD_18xx_TPDOCommPar OD entry for 0x1800+ - "TPDO communication parameter", entry is required.
|
||||
* @param OD_1Axx_TPDOMapPar OD entry for 0x1A00+ - "TPDO mapping parameter", entry is required.
|
||||
* @param CANdevTx CAN device used for PDO transmission.
|
||||
* @param CANdevTxIdx Index of transmit buffer in the above CAN device.
|
||||
* @param [out] errInfo Additional information in case of error, may be NULL.
|
||||
*
|
||||
* @return #CO_ReturnError_t CO_ERROR_NO on success.
|
||||
*/
|
||||
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) || defined CO_DOXYGEN
|
||||
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);
|
||||
|
||||
/**
|
||||
* Request transmission of TPDO message.
|
||||
*
|
||||
* If TPDO transmission type is 0, 254 or 255, then TPDO will be sent by @ref CO_TPDO_process() after inhibit timer
|
||||
* expires. See also @ref OD_requestTPDO() and @ref OD_TPDOtransmitted().
|
||||
*
|
||||
* @param TPDO TPDO object.
|
||||
*/
|
||||
static inline void
|
||||
CO_TPDOsendRequest(CO_TPDO_t* TPDO) {
|
||||
if (TPDO != NULL) {
|
||||
TPDO->sendRequest = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process transmitting PDO messages.
|
||||
*
|
||||
* Function must be called cyclically in any NMT state. It prepares and sends TPDO if necessary.
|
||||
*
|
||||
* @param TPDO This object.
|
||||
* @param timeDifference_us Time difference from previous function call.
|
||||
* @param [out] timerNext_us info to OS - see CO_process().
|
||||
* @param NMTisOperational True if this node is in NMT_OPERATIONAL state.
|
||||
* @param syncWas True, if CANopen SYNC message was just received or transmitted.
|
||||
*/
|
||||
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);
|
||||
#endif /* (CO_CONFIG_PDO) & CO_CONFIG_TPDO_ENABLE */
|
||||
|
||||
/** @} */ /* CO_PDO */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_PDO) & (CO_CONFIG_RPDO_ENABLE | CO_CONFIG_TPDO_ENABLE) */
|
||||
|
||||
#endif /* CO_PDO_H */
|
||||
1777
Middleware/CANopenNode/301/CO_SDOclient.c
Normal file
1777
Middleware/CANopenNode/301/CO_SDOclient.c
Normal file
File diff suppressed because it is too large
Load Diff
427
Middleware/CANopenNode/301/CO_SDOclient.h
Normal file
427
Middleware/CANopenNode/301/CO_SDOclient.h
Normal file
@@ -0,0 +1,427 @@
|
||||
/**
|
||||
* CANopen Service Data Object - client protocol.
|
||||
*
|
||||
* @file CO_SDOclient.h
|
||||
* @ingroup CO_SDOclient
|
||||
* @author Janez Paternoster
|
||||
* @author Matej Severkar
|
||||
* @copyright 2020 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.
|
||||
*/
|
||||
|
||||
#ifndef CO_SDO_CLIENT_H
|
||||
#define CO_SDO_CLIENT_H
|
||||
|
||||
#include "301/CO_driver.h"
|
||||
#include "301/CO_ODinterface.h"
|
||||
#include "301/CO_SDOserver.h"
|
||||
#include "301/CO_fifo.h"
|
||||
|
||||
/* default configuration, see CO_config.h */
|
||||
#ifndef CO_CONFIG_SDO_CLI
|
||||
#define CO_CONFIG_SDO_CLI (0)
|
||||
#endif
|
||||
|
||||
#ifndef CO_DOXYGEN
|
||||
#ifndef CO_CONFIG_SDO_CLI_BUFFER_SIZE
|
||||
#if ((CO_CONFIG_SDO_CLI)&CO_CONFIG_SDO_CLI_BLOCK) != 0
|
||||
#define CO_CONFIG_SDO_CLI_BUFFER_SIZE 1000U
|
||||
#else
|
||||
#define CO_CONFIG_SDO_CLI_BUFFER_SIZE 32U
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_SDO_CLI)&CO_CONFIG_SDO_CLI_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_SDOclient SDO client
|
||||
* CANopen Service Data Object - client protocol.
|
||||
*
|
||||
* @ingroup CO_CANopen_301
|
||||
* @{
|
||||
* SDO client is able to access Object Dictionary variables from remote nodes. Usually there is one SDO client on
|
||||
* CANopen network, which is able to configure other CANopen nodes. It is also possible to establish individual SDO
|
||||
* client-server communication channels between devices.
|
||||
*
|
||||
* SDO client is used in CANopenNode from CO_gateway_ascii.c with default SDO CAN identifiers. There is quite advanced
|
||||
* usage in non-blocking function.
|
||||
*
|
||||
* If enabled, SDO client is initialized in CANopen.c file with @ref CO_SDOclient_init() function.
|
||||
*
|
||||
* Basic usage:
|
||||
* @code{.c}
|
||||
CO_SDO_abortCode_t
|
||||
read_SDO(CO_SDOclient_t* SDO_C, uint8_t nodeId, uint16_t index, uint8_t subIndex, uint8_t* buf, size_t bufSize,
|
||||
size_t* readSize) {
|
||||
CO_SDO_return_t SDO_ret;
|
||||
|
||||
// setup client (this can be skipped, if remote device don't change)
|
||||
SDO_ret = CO_SDOclient_setup(SDO_C, CO_CAN_ID_SDO_CLI + nodeId, CO_CAN_ID_SDO_SRV + nodeId, nodeId);
|
||||
if (SDO_ret != CO_SDO_RT_ok_communicationEnd) {
|
||||
return CO_SDO_AB_GENERAL;
|
||||
}
|
||||
|
||||
// initiate upload
|
||||
SDO_ret = CO_SDOclientUploadInitiate(SDO_C, index, subIndex, 1000, false);
|
||||
if (SDO_ret != CO_SDO_RT_ok_communicationEnd) {
|
||||
return CO_SDO_AB_GENERAL;
|
||||
}
|
||||
|
||||
// upload data
|
||||
do {
|
||||
uint32_t timeDifference_us = 10000;
|
||||
CO_SDO_abortCode_t abortCode = CO_SDO_AB_NONE;
|
||||
|
||||
SDO_ret = CO_SDOclientUpload(SDO_C, timeDifference_us, false, &abortCode, NULL, NULL, NULL);
|
||||
if (SDO_ret < 0) {
|
||||
return abortCode;
|
||||
}
|
||||
|
||||
sleep_us(timeDifference_us);
|
||||
} while (SDO_ret > 0);
|
||||
|
||||
// copy data to the user buffer (for long data function must be called several times inside the loop)
|
||||
*readSize = CO_SDOclientUploadBufRead(SDO_C, buf, bufSize);
|
||||
|
||||
return CO_SDO_AB_NONE;
|
||||
}
|
||||
|
||||
CO_SDO_abortCode_t
|
||||
write_SDO(CO_SDOclient_t* SDO_C, uint8_t nodeId, uint16_t index, uint8_t subIndex, uint8_t* data, size_t dataSize) {
|
||||
CO_SDO_return_t SDO_ret;
|
||||
bool_t bufferPartial = false;
|
||||
|
||||
// setup client (this can be skipped, if remote device is the same)
|
||||
SDO_ret = CO_SDOclient_setup(SDO_C, CO_CAN_ID_SDO_CLI + nodeId, CO_CAN_ID_SDO_SRV + nodeId, nodeId);
|
||||
if (SDO_ret != CO_SDO_RT_ok_communicationEnd) {
|
||||
return -1
|
||||
}
|
||||
|
||||
// initiate download
|
||||
SDO_ret = CO_SDOclientDownloadInitiate(SDO_C, index, subIndex, dataSize, 1000, false);
|
||||
if (SDO_ret != CO_SDO_RT_ok_communicationEnd) {
|
||||
return -1
|
||||
}
|
||||
|
||||
// fill data
|
||||
size_t nWritten = CO_SDOclientDownloadBufWrite(SDO_C, data, dataSize);
|
||||
if (nWritten < dataSize) {
|
||||
bufferPartial = true;
|
||||
// If SDO Fifo buffer is too small, data can be refilled in the loop.
|
||||
}
|
||||
|
||||
// download data
|
||||
do {
|
||||
uint32_t timeDifference_us = 10000;
|
||||
CO_SDO_abortCode_t abortCode = CO_SDO_AB_NONE;
|
||||
|
||||
SDO_ret = CO_SDOclientDownload(SDO_C, timeDifference_us, false, bufferPartial, &abortCode, NULL, NULL);
|
||||
if (SDO_ret < 0) {
|
||||
return abortCode;
|
||||
}
|
||||
|
||||
sleep_us(timeDifference_us);
|
||||
} while (SDO_ret > 0);
|
||||
|
||||
return CO_SDO_AB_NONE;
|
||||
}
|
||||
* @endcode
|
||||
*
|
||||
* @see @ref CO_SDOserver
|
||||
*/
|
||||
|
||||
/**
|
||||
* SDO client object
|
||||
*/
|
||||
typedef struct {
|
||||
#if (((CO_CONFIG_SDO_CLI)&CO_CONFIG_SDO_CLI_LOCAL) != 0) || defined CO_DOXYGEN
|
||||
OD_t* OD; /**< From CO_SDOclient_init() */
|
||||
uint8_t nodeId; /**< From CO_SDOclient_init() */
|
||||
OD_IO_t OD_IO; /**< Object dictionary interface for locally transferred object */
|
||||
#endif
|
||||
CO_CANmodule_t* CANdevRx; /**< From CO_SDOclient_init() */
|
||||
uint16_t CANdevRxIdx; /**< From CO_SDOclient_init() */
|
||||
CO_CANmodule_t* CANdevTx; /**< From CO_SDOclient_init() */
|
||||
uint16_t CANdevTxIdx; /**< From CO_SDOclient_init() */
|
||||
CO_CANtx_t* CANtxBuff; /**< CAN transmit buffer inside CANdevTx for CAN tx message */
|
||||
#if (((CO_CONFIG_SDO_CLI)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0) || defined CO_DOXYGEN
|
||||
|
||||
uint32_t COB_IDClientToServer; /**< Copy of CANopen COB_ID Client -> Server, meaning of the specific bits:
|
||||
- Bit 0...10: 11-bit CAN identifier.
|
||||
- Bit 11..30: reserved, must be 0.
|
||||
- Bit 31: if 1, SDO client object is not used. */
|
||||
uint32_t COB_IDServerToClient; /**< Copy of CANopen COB_ID Server -> Client, similar as above */
|
||||
OD_extension_t OD_1280_extension; /**< Extension for OD object */
|
||||
#endif
|
||||
uint8_t nodeIDOfTheSDOServer; /**< Node-ID of the SDO server */
|
||||
bool_t valid; /**< If true, SDO channel is valid */
|
||||
uint16_t index; /**< Index of current object in Object Dictionary */
|
||||
uint8_t subIndex; /**< Subindex of current object in Object Dictionary */
|
||||
bool_t finished; /**< If true, then data transfer is finished */
|
||||
size_t sizeInd; /**< Size of data, which will be transferred. It is optionally indicated by client
|
||||
in case of download or by server in case of upload. */
|
||||
size_t sizeTran; /**< Size of data which is actually transferred. */
|
||||
volatile CO_SDO_state_t state; /**< Internal state of the SDO client */
|
||||
uint32_t SDOtimeoutTime_us; /**< Maximum timeout time between request and response in microseconds */
|
||||
uint32_t timeoutTimer; /**< Timeout timer for SDO communication */
|
||||
CO_fifo_t bufFifo; /**< CO_fifo_t object for data buffer (not pointer) */
|
||||
uint8_t buf[CO_CONFIG_SDO_CLI_BUFFER_SIZE + 1U]; /**< Data buffer of usable size @ref CO_CONFIG_SDO_CLI_BUFFER_SIZE,
|
||||
used inside bufFifo. Must be one byte larger for fifo usage. */
|
||||
volatile void* CANrxNew; /**< Indicates, if new SDO message received from CAN bus. It is not cleared, until received
|
||||
message is completely processed. */
|
||||
uint8_t CANrxData[8]; /**< 8 data bytes of the received message */
|
||||
#if (((CO_CONFIG_SDO_CLI)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
void (*pFunctSignal)(void* object); /**< From CO_SDOclient_initCallbackPre() or NULL */
|
||||
void* functSignalObject; /**< From CO_SDOclient_initCallbackPre() or NULL */
|
||||
#endif
|
||||
#if (((CO_CONFIG_SDO_CLI)&CO_CONFIG_SDO_CLI_SEGMENTED) != 0) || defined CO_DOXYGEN
|
||||
uint8_t toggle; /**< Toggle bit toggled in each segment in segmented transfer */
|
||||
#endif
|
||||
#if (((CO_CONFIG_SDO_CLI)&CO_CONFIG_SDO_CLI_BLOCK) != 0) || defined CO_DOXYGEN
|
||||
uint32_t block_SDOtimeoutTime_us; /**< Timeout time for SDO sub-block upload, half of #SDOtimeoutTime_us */
|
||||
uint32_t block_timeoutTimer; /**< Timeout timer for SDO sub-block upload */
|
||||
uint8_t block_seqno; /**< Sequence number of segment in block, 1..127 */
|
||||
uint8_t block_blksize; /**< Number of segments per block, 1..127 */
|
||||
uint8_t block_noData; /**< Number of bytes in last segment that do not contain data */
|
||||
bool_t block_crcEnabled; /**< Server CRC support in block transfer */
|
||||
uint8_t block_dataUploadLast[7]; /**< Last 7 bytes of data at block upload */
|
||||
uint16_t block_crc; /**< Calculated CRC checksum */
|
||||
#endif
|
||||
} CO_SDOclient_t;
|
||||
|
||||
/**
|
||||
* Initialize SDO client object.
|
||||
*
|
||||
* Function must be called in the communication reset section.
|
||||
*
|
||||
* @param SDO_C This object will be initialized.
|
||||
* @param OD Object Dictionary. It is used in case, if client is accessing object dictionary from its own device. If
|
||||
* NULL, it will be ignored.
|
||||
* @param OD_1280_SDOcliPar OD entry for SDO client parameter (0x1280+). It may have IO extension enabled to allow
|
||||
* dynamic configuration (see also
|
||||
* @ref CO_CONFIG_FLAG_OD_DYNAMIC). Entry is required.
|
||||
* @param nodeId CANopen Node ID of this device. It is used in case, if client is accessing object dictionary from its
|
||||
* own device. If 0, it will be ignored.
|
||||
* @param CANdevRx CAN device for SDO client reception.
|
||||
* @param CANdevRxIdx Index of receive buffer in the above CAN device.
|
||||
* @param CANdevTx CAN device for SDO client transmission.
|
||||
* @param CANdevTxIdx Index of transmit buffer in the above CAN device.
|
||||
* @param [out] errInfo Additional information in case of error, may be NULL.
|
||||
*
|
||||
* @return #CO_ReturnError_t: CO_ERROR_NO or CO_ERROR_ILLEGAL_ARGUMENT.
|
||||
*/
|
||||
CO_ReturnError_t CO_SDOclient_init(CO_SDOclient_t* SDO_C, OD_t* OD, OD_entry_t* OD_1280_SDOcliPar, uint8_t nodeId,
|
||||
CO_CANmodule_t* CANdevRx, uint16_t CANdevRxIdx, CO_CANmodule_t* CANdevTx,
|
||||
uint16_t CANdevTxIdx, uint32_t* errInfo);
|
||||
|
||||
#if (((CO_CONFIG_SDO_CLI)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Initialize SDOclient callback function.
|
||||
*
|
||||
* Function initializes optional callback function, which should immediately start processing of CO_SDOclientDownload()
|
||||
* or CO_SDOclientUpload() function. Callback is called after SDOclient message is received from the CAN bus or when new
|
||||
* call without delay is necessary (exchange data with own SDO server or SDO block transfer is in progress).
|
||||
*
|
||||
* @param SDOclient This object.
|
||||
* @param object Pointer to object, which will be passed to pFunctSignal(). Can be NULL.
|
||||
* @param pFunctSignal Pointer to the callback function. Not called if NULL.
|
||||
*/
|
||||
void CO_SDOclient_initCallbackPre(CO_SDOclient_t* SDOclient, void* object, void (*pFunctSignal)(void* object));
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Setup SDO client object.
|
||||
*
|
||||
* Function is called in from CO_SDOclient_init() and each time when "SDO client parameter" is written. Application can
|
||||
* call this function before new SDO communication. If parameters to this function are the same as before, then CAN is
|
||||
* not reconfigured.
|
||||
*
|
||||
* @param SDO_C This object.
|
||||
* @param COB_IDClientToServer See @ref CO_SDOclient_t.
|
||||
* @param COB_IDServerToClient See @ref CO_SDOclient_t.
|
||||
* @param nodeIDOfTheSDOServer Node-ID of the SDO server. If it is the same as node-ID of this node, then data will be
|
||||
* exchanged with this node (without CAN communication).
|
||||
*
|
||||
* @return #CO_SDO_return_t, CO_SDO_RT_ok_communicationEnd or CO_SDO_RT_wrongArguments
|
||||
*/
|
||||
CO_SDO_return_t CO_SDOclient_setup(CO_SDOclient_t* SDO_C, uint32_t COB_IDClientToServer, uint32_t COB_IDServerToClient,
|
||||
uint8_t nodeIDOfTheSDOServer);
|
||||
|
||||
/**
|
||||
* Initiate SDO download communication.
|
||||
*
|
||||
* Function initiates SDO download communication with server specified in CO_SDOclient_init() function. Data will be
|
||||
* written to remote node. Function is non-blocking.
|
||||
*
|
||||
* @param SDO_C This object.
|
||||
* @param index Index of object in object dictionary in remote node.
|
||||
* @param subIndex Subindex of object in object dictionary in remote node.
|
||||
* @param sizeIndicated Optionally indicate size of data to be downloaded. Actual data are written with one or multiple
|
||||
* CO_SDOclientDownloadBufWrite() calls.
|
||||
* - If sizeIndicated is different than 0, then total number of data written by CO_SDOclientDownloadBufWrite() will be
|
||||
* compared against sizeIndicated. Also sizeIndicated info will be passed to the server, which will compare actual
|
||||
* data size downloaded. In case of mismatch, SDO abort message will be generated.
|
||||
* - If sizeIndicated is 0, then actual data size will not be verified.
|
||||
* @param SDOtimeoutTime_ms Timeout time for SDO communication in milliseconds.
|
||||
* @param blockEnable Try to initiate block transfer.
|
||||
*
|
||||
* @return #CO_SDO_return_t
|
||||
*/
|
||||
CO_SDO_return_t CO_SDOclientDownloadInitiate(CO_SDOclient_t* SDO_C, uint16_t index, uint8_t subIndex,
|
||||
size_t sizeIndicated, uint16_t SDOtimeoutTime_ms, bool_t blockEnable);
|
||||
|
||||
/**
|
||||
* Initiate SDO download communication - update size.
|
||||
*
|
||||
* This is optional function, which updates sizeIndicated, if it was not known in the CO_SDOclientDownloadInitiate()
|
||||
* function call. This function can be used after CO_SDOclientDownloadBufWrite(), but must be used before
|
||||
* CO_SDOclientDownload().
|
||||
*
|
||||
* @param SDO_C This object.
|
||||
* @param sizeIndicated Same as in CO_SDOclientDownloadInitiate().
|
||||
*/
|
||||
void CO_SDOclientDownloadInitSize(CO_SDOclient_t* SDO_C, size_t sizeIndicated);
|
||||
|
||||
/**
|
||||
* Write data into SDO client buffer
|
||||
*
|
||||
* This function copies data from buf into internal SDO client fifo buffer. Function returns number of bytes
|
||||
* successfully copied. If there is not enough space in destination, not all bytes will be copied. Additional data can
|
||||
* be copied in next cycles. If there is enough space in destination and sizeIndicated is different than zero, then all
|
||||
* data must be written at once.
|
||||
*
|
||||
* This function is basically a wrapper for CO_fifo_write() function. As alternative, other functions from CO_fifo can
|
||||
* be used directly, for example CO_fifo_cpyTok2U8() or similar.
|
||||
*
|
||||
* @param SDO_C This object.
|
||||
* @param buf Buffer which will be copied
|
||||
* @param count Number of bytes in buf
|
||||
*
|
||||
* @return number of bytes actually written.
|
||||
*/
|
||||
size_t CO_SDOclientDownloadBufWrite(CO_SDOclient_t* SDO_C, const uint8_t* buf, size_t count);
|
||||
|
||||
/**
|
||||
* Process SDO download communication.
|
||||
*
|
||||
* Function must be called cyclically until it returns <=0. It Proceeds SDO download communication initiated with
|
||||
* CO_SDOclientDownloadInitiate(). Function is non-blocking.
|
||||
*
|
||||
* If function returns #CO_SDO_RT_blockDownldInProgress and OS has buffer for CAN tx messages, then this function may be
|
||||
* called multiple times within own loop. This can speed-up SDO block transfer.
|
||||
*
|
||||
* @param SDO_C This object.
|
||||
* @param timeDifference_us Time difference from previous function call in [microseconds].
|
||||
* @param send_abort If true, SDO client will send abort message from SDOabortCode and transmission will be aborted.
|
||||
* @param bufferPartial True indicates, not all data were copied to internal buffer yet. Buffer will be refilled later
|
||||
* with #CO_SDOclientDownloadBufWrite.
|
||||
* @param [out] SDOabortCode In case of error in communication, SDO abort code contains reason of error. Ignored if
|
||||
* NULL.
|
||||
* @param [out] sizeTransferred Actual size of data transferred. Ignored if NULL
|
||||
* @param [out] timerNext_us info to OS - see CO_process(). Ignored if NULL.
|
||||
*
|
||||
* @return #CO_SDO_return_t. If less than 0, then error occurred, SDOabortCode contains reason and state becomes idle.
|
||||
* If 0, communication ends successfully and state becomes idle. If greater than 0, then communication is in progress.
|
||||
*/
|
||||
CO_SDO_return_t CO_SDOclientDownload(CO_SDOclient_t* SDO_C, uint32_t timeDifference_us, bool_t send_abort,
|
||||
bool_t bufferPartial, CO_SDO_abortCode_t* SDOabortCode, size_t* sizeTransferred,
|
||||
uint32_t* timerNext_us);
|
||||
|
||||
/**
|
||||
* Initiate SDO upload communication.
|
||||
*
|
||||
* Function initiates SDO upload communication with server specified in CO_SDOclient_init() function. Data will be read
|
||||
* from remote node. Function is non-blocking.
|
||||
*
|
||||
* @param SDO_C This object.
|
||||
* @param index Index of object in object dictionary in remote node.
|
||||
* @param subIndex Subindex of object in object dictionary in remote node.
|
||||
* @param SDOtimeoutTime_ms Timeout time for SDO communication in milliseconds.
|
||||
* @param blockEnable Try to initiate block transfer.
|
||||
*
|
||||
* @return #CO_SDO_return_t
|
||||
*/
|
||||
CO_SDO_return_t CO_SDOclientUploadInitiate(CO_SDOclient_t* SDO_C, uint16_t index, uint8_t subIndex,
|
||||
uint16_t SDOtimeoutTime_ms, bool_t blockEnable);
|
||||
|
||||
/**
|
||||
* Process SDO upload communication.
|
||||
*
|
||||
* Function must be called cyclically until it returns <=0. It Proceeds SDO upload communication initiated with
|
||||
* CO_SDOclientUploadInitiate(). Function is non-blocking.
|
||||
*
|
||||
* If this function returns #CO_SDO_RT_uploadDataBufferFull, then data must be read from fifo buffer to make it empty.
|
||||
* This function can then be called once again immediately to speed-up block transfer. Note also, that remaining data
|
||||
* must be read after function returns #CO_SDO_RT_ok_communicationEnd. Data must not be read, if function returns
|
||||
* #CO_SDO_RT_blockUploadInProgress.
|
||||
*
|
||||
* @param SDO_C This object.
|
||||
* @param timeDifference_us Time difference from previous function call in [microseconds].
|
||||
* @param send_abort If true, SDO client will send abort message from SDOabortCode and reception will be aborted.
|
||||
* @param [out] SDOabortCode In case of error in communication, SDO abort code contains reason of error. Ignored if
|
||||
* NULL.
|
||||
* @param [out] sizeIndicated If larger than 0, then SDO server has indicated size of data transfer. Ignored if NULL.
|
||||
* @param [out] sizeTransferred Actual size of data transferred. Ignored if NULL
|
||||
* @param [out] timerNext_us info to OS - see CO_process(). Ignored if NULL.
|
||||
*
|
||||
* @return #CO_SDO_return_t. If less than 0, then error occurred, SDOabortCode contains reason and state becomes idle.
|
||||
* If 0, communication ends successfully and state becomes idle. If greater than 0, then communication is in progress.
|
||||
*/
|
||||
CO_SDO_return_t CO_SDOclientUpload(CO_SDOclient_t* SDO_C, uint32_t timeDifference_us, bool_t send_abort,
|
||||
CO_SDO_abortCode_t* SDOabortCode, size_t* sizeIndicated, size_t* sizeTransferred,
|
||||
uint32_t* timerNext_us);
|
||||
|
||||
/**
|
||||
* Read data from SDO client buffer.
|
||||
*
|
||||
* This function copies data from internal fifo buffer of SDO client into buf. Function returns number of bytes
|
||||
* successfully copied. It can be called in multiple cycles, if data length is large.
|
||||
*
|
||||
* This function is basically a wrapper for CO_fifo_read() function. As alternative, other functions from CO_fifo can be
|
||||
* used directly, for example CO_fifo_readU82a() or similar.
|
||||
*
|
||||
* @warning This function (or similar) must NOT be called when CO_SDOclientUpload() returns
|
||||
* #CO_SDO_RT_blockUploadInProgress!
|
||||
*
|
||||
* @param SDO_C This object.
|
||||
* @param buf Buffer into which data will be copied
|
||||
* @param count Copy up to count bytes into buffer
|
||||
*
|
||||
* @return number of bytes actually read.
|
||||
*/
|
||||
size_t CO_SDOclientUploadBufRead(CO_SDOclient_t* SDO_C, uint8_t* buf, size_t count);
|
||||
|
||||
/**
|
||||
* Close SDO communication temporary.
|
||||
*
|
||||
* Function must be called after finish of each SDO client communication cycle. It disables reception of SDO client CAN
|
||||
* messages. It is necessary, because CO_SDOclient_receive function may otherwise write into undefined SDO buffer.
|
||||
*
|
||||
* @param SDO_C This object.
|
||||
*/
|
||||
void CO_SDOclientClose(CO_SDOclient_t* SDO_C);
|
||||
|
||||
/** @} */ /* CO_SDOclient */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_SDO_CLI) & CO_CONFIG_SDO_CLI_ENABLE */
|
||||
|
||||
#endif /* CO_SDO_CLIENT_H */
|
||||
1802
Middleware/CANopenNode/301/CO_SDOserver.c
Normal file
1802
Middleware/CANopenNode/301/CO_SDOserver.c
Normal file
File diff suppressed because it is too large
Load Diff
473
Middleware/CANopenNode/301/CO_SDOserver.h
Normal file
473
Middleware/CANopenNode/301/CO_SDOserver.h
Normal file
@@ -0,0 +1,473 @@
|
||||
/**
|
||||
* CANopen Service Data Object - server protocol.
|
||||
*
|
||||
* @file CO_SDOserver.h
|
||||
* @ingroup CO_SDOserver
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2020 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.
|
||||
*/
|
||||
|
||||
#ifndef CO_SDO_SERVER_H
|
||||
#define CO_SDO_SERVER_H
|
||||
|
||||
#include "301/CO_driver.h"
|
||||
#include "301/CO_ODinterface.h"
|
||||
|
||||
/* default configuration, see CO_config.h */
|
||||
#ifndef CO_CONFIG_SDO_SRV
|
||||
#define CO_CONFIG_SDO_SRV \
|
||||
(CO_CONFIG_SDO_SRV_SEGMENTED | CO_CONFIG_GLOBAL_FLAG_CALLBACK_PRE | CO_CONFIG_GLOBAL_FLAG_TIMERNEXT \
|
||||
| CO_CONFIG_GLOBAL_FLAG_OD_DYNAMIC)
|
||||
#endif
|
||||
#ifndef CO_CONFIG_SDO_SRV_BUFFER_SIZE
|
||||
#define CO_CONFIG_SDO_SRV_BUFFER_SIZE 32U
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_SDOserver SDO server
|
||||
* CANopen Service Data Object - server protocol.
|
||||
*
|
||||
* @ingroup CO_CANopen_301
|
||||
* @{
|
||||
* Service data objects (SDOs) allow the access to any entry of the CANopen Object dictionary. By SDO a peer-to-peer
|
||||
* communication channel between two CANopen devices is established. In addition, the SDO protocol enables to transfer
|
||||
* any amount of data in a segmented way. Therefore the SDO protocol is mainly used in order to communicate
|
||||
* configuration data.
|
||||
*
|
||||
* All CANopen devices must have implemented SDO server and first SDO server channel. Servers serves data from Object
|
||||
* dictionary. Object dictionary is a collection of variables, arrays or records (structures), which can be used by the
|
||||
* stack or by the application. This file (CO_SDOserver.h) implements SDO server.
|
||||
*
|
||||
* SDO client can be (optionally) implemented on one (or multiple, if multiple SDO channels are used) device in CANopen
|
||||
* network. Usually this is master device and provides also some kind of user interface, so configuration of the network
|
||||
* is possible. Code for the SDO client is in file CO_SDOclient.h.
|
||||
*
|
||||
* SDO communication cycle is initiated by the client. Client can upload (read) data from device or can download (write)
|
||||
* data to device. If data size is less or equal to 4 bytes, communication is finished by one server response (expedited
|
||||
* transfer). If data size is longer, data are split into multiple segments of request/response pairs (normal or
|
||||
* segmented transfer). For longer data there is also a block transfer protocol, which transfers larger block of data in
|
||||
* secure way with little protocol overhead. If error occurs during SDO transfer #CO_SDO_abortCode_t is send by client
|
||||
* or server and transfer is terminated. For more details see #CO_SDO_state_t.
|
||||
*
|
||||
* Access to Object dictionary is specified in @ref CO_ODinterface.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal state flags indicate type of transfer
|
||||
*
|
||||
* These flags correspond to the upper nibble of the SDO state machine states and can be used to determine the type of
|
||||
* state an SDO object is in.
|
||||
*/
|
||||
#define CO_SDO_ST_FLAG_DOWNLOAD 0x10U
|
||||
#define CO_SDO_ST_FLAG_UPLOAD 0x20U
|
||||
#define CO_SDO_ST_FLAG_BLOCK 0x40U
|
||||
|
||||
/**
|
||||
* Internal states of the SDO state machine.
|
||||
*
|
||||
* Upper nibble of byte indicates type of state:
|
||||
* 0x10: Download
|
||||
* 0x20: Upload
|
||||
* 0x40: Block Mode
|
||||
*
|
||||
* Note: CANopen has little endian byte order.
|
||||
*/
|
||||
typedef enum {
|
||||
/**
|
||||
* - SDO client may start new download to or upload from specified node, specified index and specified subindex. It
|
||||
* can start normal or block communication.
|
||||
* - SDO server is waiting for client request. */
|
||||
CO_SDO_ST_IDLE = 0x00U,
|
||||
/**
|
||||
* - SDO client or server may send SDO abort message in case of error:
|
||||
* - byte 0: @b 10000000 binary.
|
||||
* - byte 1..3: Object index and subIndex.
|
||||
* - byte 4..7: #CO_SDO_abortCode_t. */
|
||||
CO_SDO_ST_ABORT = 0x01U,
|
||||
|
||||
/**
|
||||
* - SDO client: Node-ID of the SDO server is the same as node-ID of this node, SDO client is the same device as
|
||||
* SDO server. Transfer data directly without communication on CAN.
|
||||
* - SDO server does not use this state. */
|
||||
CO_SDO_ST_DOWNLOAD_LOCAL_TRANSFER = 0x10U,
|
||||
/**
|
||||
* - SDO client initiates SDO download:
|
||||
* - byte 0: @b 0010nnes binary: (nn: if e=s=1, number of data bytes, that do @b not contain data; e=1 for
|
||||
* expedited transfer; s=1 if data size is indicated.)
|
||||
* - byte 1..3: Object index and subIndex.
|
||||
* - byte 4..7: If e=1, expedited data are here. If e=0 s=1, size of data for segmented transfer is indicated here.
|
||||
* - SDO server is in #CO_SDO_ST_IDLE state and waits for client request. */
|
||||
CO_SDO_ST_DOWNLOAD_INITIATE_REQ = 0x11U,
|
||||
/**
|
||||
* - SDO client waits for response.
|
||||
* - SDO server responses:
|
||||
* - byte 0: @b 01100000 binary.
|
||||
* - byte 1..3: Object index and subIndex.
|
||||
* - byte 4..7: Reserved.
|
||||
* - In case of expedited transfer communication ends here. */
|
||||
CO_SDO_ST_DOWNLOAD_INITIATE_RSP = 0x12U,
|
||||
/**
|
||||
* - SDO client sends SDO segment:
|
||||
* - byte 0: @b 000tnnnc binary: (t: toggle bit, set to 0 in first segment; nnn: number of data bytes, that do
|
||||
* @b not contain data; c=1 if this is the last segment).
|
||||
* - byte 1..7: Data segment.
|
||||
* - SDO server waits for segment. */
|
||||
CO_SDO_ST_DOWNLOAD_SEGMENT_REQ = 0x13U,
|
||||
/**
|
||||
* - SDO client waits for response.
|
||||
* - SDO server responses:
|
||||
* - byte 0: @b 001t0000 binary: (t: toggle bit, set to 0 in first segment).
|
||||
* - byte 1..7: Reserved.
|
||||
* - If c was set to 1, then communication ends here. */
|
||||
CO_SDO_ST_DOWNLOAD_SEGMENT_RSP = 0x14U,
|
||||
|
||||
/**
|
||||
* - SDO client: Node-ID of the SDO server is the same as node-ID of this node, SDO client is the same device as
|
||||
* SDO server. Transfer data directly without communication on CAN.
|
||||
* - SDO server does not use this state. */
|
||||
CO_SDO_ST_UPLOAD_LOCAL_TRANSFER = 0x20U,
|
||||
/**
|
||||
* - SDO client initiates SDO upload:
|
||||
* - byte 0: @b 01000000 binary.
|
||||
* - byte 1..3: Object index and subIndex.
|
||||
* - byte 4..7: Reserved.
|
||||
* - SDO server is in #CO_SDO_ST_IDLE state and waits for client request. */
|
||||
CO_SDO_ST_UPLOAD_INITIATE_REQ = 0x21U,
|
||||
/**
|
||||
* - SDO client waits for response.
|
||||
* - SDO server responses:
|
||||
* - byte 0: @b 0100nnes binary: (nn: if e=s=1, number of data bytes, that do @b not contain data; e=1 for
|
||||
* expedited transfer; s=1 if data size is indicated).
|
||||
* - byte 1..3: Object index and subIndex.
|
||||
* - byte 4..7: If e=1, expedited data are here. If e=0 s=1, size of data for segmented transfer is indicated here.
|
||||
* - In case of expedited transfer communication ends here. */
|
||||
CO_SDO_ST_UPLOAD_INITIATE_RSP = 0x22U,
|
||||
/**
|
||||
* - SDO client requests SDO segment:
|
||||
* - byte 0: @b 011t0000 binary: (t: toggle bit, set to 0 in first segment).
|
||||
* - byte 1..7: Reserved.
|
||||
* - SDO server waits for segment request. */
|
||||
CO_SDO_ST_UPLOAD_SEGMENT_REQ = 0x23U,
|
||||
/**
|
||||
* - SDO client waits for response.
|
||||
* - SDO server responses with data:
|
||||
* - byte 0: @b 000tnnnc binary: (t: toggle bit, set to 0 in first segment; nnn: number of data bytes, that do
|
||||
* @b not contain data; c=1 if this is the last segment).
|
||||
* - byte 1..7: Data segment.
|
||||
* - If c is set to 1, then communication ends here. */
|
||||
CO_SDO_ST_UPLOAD_SEGMENT_RSP = 0x24U,
|
||||
|
||||
/**
|
||||
* - SDO client initiates SDO block download:
|
||||
* - byte 0: @b 11000rs0 binary: (r=1 if client supports generating CRC on data; s=1 if data size is indicated.)
|
||||
* - byte 1..3: Object index and subIndex.
|
||||
* - byte 4..7: If s=1, then size of data for block download is indicated here.
|
||||
* - SDO server is in #CO_SDO_ST_IDLE state and waits for client request. */
|
||||
CO_SDO_ST_DOWNLOAD_BLK_INITIATE_REQ = 0x51U,
|
||||
/**
|
||||
* - SDO client waits for response.
|
||||
* - SDO server responses:
|
||||
* - byte 0: @b 10100r00 binary: (r=1 if server supports generating CRC on data.)
|
||||
* - byte 1..3: Object index and subIndex.
|
||||
* - byte 4: blksize: Number of segments per block that shall be used by the client for the following block
|
||||
* download with 0 < blksize < 128.
|
||||
* - byte 5..7: Reserved. */
|
||||
CO_SDO_ST_DOWNLOAD_BLK_INITIATE_RSP = 0x52U,
|
||||
/**
|
||||
* - SDO client sends 'blksize' segments of data in sequence:
|
||||
* - byte 0: @b cnnnnnnn binary: (c=1 if no more segments to be downloaded, enter SDO block download end phase;
|
||||
* nnnnnnn is sequence number of segment, 1..127.
|
||||
* - byte 1..7: At most 7 bytes of segment data to be downloaded.
|
||||
* - SDO server reads sequence of 'blksize' blocks. */
|
||||
CO_SDO_ST_DOWNLOAD_BLK_SUBBLOCK_REQ = 0x53U,
|
||||
/**
|
||||
* - SDO client waits for response.
|
||||
* - SDO server responses:
|
||||
* - byte 0: @b 10100010 binary.
|
||||
* - byte 1: ackseq: sequence number of last segment that was received successfully during the last block
|
||||
* download. If ackseq is set to 0 the server indicates the client that the segment with the sequence number 1
|
||||
* was not received correctly and all segments shall be retransmitted by the client.
|
||||
* - byte 2: Number of segments per block that shall be used by the client for the following block download with
|
||||
* 0 < blksize < 128.
|
||||
* - byte 3..7: Reserved.
|
||||
* - If c was set to 1, then communication enters SDO block download end phase.
|
||||
*/
|
||||
CO_SDO_ST_DOWNLOAD_BLK_SUBBLOCK_RSP = 0x54U,
|
||||
/**
|
||||
* - SDO client sends SDO block download end:
|
||||
* - byte 0: @b 110nnn01 binary: (nnn: number of data bytes, that do @b not contain data)
|
||||
* - byte 1..2: 16 bit CRC for the data set, if enabled by client and server.
|
||||
* - byte 3..7: Reserved.
|
||||
* - SDO server waits for client request. */
|
||||
CO_SDO_ST_DOWNLOAD_BLK_END_REQ = 0x55U,
|
||||
/**
|
||||
* - SDO client waits for response.
|
||||
* - SDO server responses:
|
||||
* - byte 0: @b 10100001 binary.
|
||||
* - byte 1..7: Reserved.
|
||||
* - Block download successfully ends here.
|
||||
*/
|
||||
CO_SDO_ST_DOWNLOAD_BLK_END_RSP = 0x56U,
|
||||
|
||||
/**
|
||||
* - SDO client initiates SDO block upload:
|
||||
* - byte 0: @b 10100r00 binary: (r=1 if client supports generating CRC on data.)
|
||||
* - byte 1..3: Object index and subIndex.
|
||||
* - byte 4: blksize: Number of segments per block with 0 < blksize < 128.
|
||||
* - byte 5: pst - protocol switch threshold. If pst > 0 and size of the data in bytes is less or equal pst,
|
||||
* then the server may switch to the SDO upload protocol #CO_SDO_ST_UPLOAD_INITIATE_RSP.
|
||||
* - byte 6..7: Reserved.
|
||||
* - SDO server is in #CO_SDO_ST_IDLE state and waits for client request. */
|
||||
CO_SDO_ST_UPLOAD_BLK_INITIATE_REQ = 0x61U,
|
||||
/**
|
||||
* - SDO client waits for response.
|
||||
* - SDO server responses:
|
||||
* - byte 0: @b 11000rs0 binary: (r=1 if server supports generating CRC on data; s=1 if data size is indicated.)
|
||||
* - byte 1..3: Object index and subIndex.
|
||||
* - byte 4..7: If s=1, then size of data for block upload is indicated here.
|
||||
* - If enabled by pst, then server may alternatively response with #CO_SDO_ST_UPLOAD_INITIATE_RSP */
|
||||
CO_SDO_ST_UPLOAD_BLK_INITIATE_RSP = 0x62U,
|
||||
/**
|
||||
* - SDO client sends second initiate for SDO block upload:
|
||||
* - byte 0: @b 10100011 binary.
|
||||
* - byte 1..7: Reserved.
|
||||
* - SDO server waits for client request. */
|
||||
CO_SDO_ST_UPLOAD_BLK_INITIATE_REQ2 = 0x63U,
|
||||
/**
|
||||
* - SDO client reads sequence of 'blksize' blocks.
|
||||
* - SDO server sends 'blksize' segments of data in sequence:
|
||||
* - byte 0: @b cnnnnnnn binary: (c=1 if no more segments to be uploaded, enter SDO block upload end phase;
|
||||
* nnnnnnn is sequence number of segment, 1..127.
|
||||
* - byte 1..7: At most 7 bytes of segment data to be uploaded. */
|
||||
CO_SDO_ST_UPLOAD_BLK_SUBBLOCK_SREQ = 0x64U,
|
||||
/**
|
||||
* - SDO client responses:
|
||||
* - byte 0: @b 10100010 binary.
|
||||
* - byte 1: ackseq: sequence number of last segment that was received successfully during the last block
|
||||
* upload. If ackseq is set to 0 the client indicates the server that the segment with the sequence number 1 was
|
||||
* not received correctly and all segments shall be retransmitted by the server.
|
||||
* - byte 2: Number of segments per block that shall be used by the server for the following block upload with
|
||||
* 0 < blksize < 128.
|
||||
* - byte 3..7: Reserved.
|
||||
* - SDO server waits for response.
|
||||
* - If c was set to 1 and all segments were successfull received, then communication enters SDO block upload end
|
||||
* phase. */
|
||||
CO_SDO_ST_UPLOAD_BLK_SUBBLOCK_CRSP = 0x65U,
|
||||
/**
|
||||
* - SDO client waits for server request.
|
||||
* - SDO server sends SDO block upload end:
|
||||
* - byte 0: @b 110nnn01 binary: (nnn: number of data bytes, that do @b not contain data)
|
||||
* - byte 1..2: 16 bit CRC for the data set, if enabled by client and server.
|
||||
* - byte 3..7: Reserved. */
|
||||
CO_SDO_ST_UPLOAD_BLK_END_SREQ = 0x66U,
|
||||
/**
|
||||
* - SDO client responses:
|
||||
* - byte 0: @b 10100001 binary.
|
||||
* - byte 1..7: Reserved.
|
||||
* - SDO server waits for response.
|
||||
* - Block download successfully ends here. Note that this communication ends with client response. Client may
|
||||
* then start next SDO communication immediately.
|
||||
*/
|
||||
CO_SDO_ST_UPLOAD_BLK_END_CRSP = 0x67U,
|
||||
} CO_SDO_state_t;
|
||||
|
||||
/**
|
||||
* SDO abort codes.
|
||||
*
|
||||
* Send with Abort SDO transfer message.
|
||||
*
|
||||
* The abort codes not listed here are reserved.
|
||||
*/
|
||||
typedef enum {
|
||||
CO_SDO_AB_NONE = 0x00000000UL, /**< 0x00000000, No abort */
|
||||
CO_SDO_AB_TOGGLE_BIT = 0x05030000UL, /**< 0x05030000, Toggle bit not altered */
|
||||
CO_SDO_AB_TIMEOUT = 0x05040000UL, /**< 0x05040000, SDO protocol timed out */
|
||||
CO_SDO_AB_CMD = 0x05040001UL, /**< 0x05040001, Command specifier not valid or unknown */
|
||||
CO_SDO_AB_BLOCK_SIZE = 0x05040002UL, /**< 0x05040002, Invalid block size in block mode */
|
||||
CO_SDO_AB_SEQ_NUM = 0x05040003UL, /**< 0x05040003, Invalid sequence number in block mode */
|
||||
CO_SDO_AB_CRC = 0x05040004UL, /**< 0x05040004, CRC error (block mode only) */
|
||||
CO_SDO_AB_OUT_OF_MEM = 0x05040005UL, /**< 0x05040005, Out of memory */
|
||||
CO_SDO_AB_UNSUPPORTED_ACCESS = 0x06010000UL, /**< 0x06010000, Unsupported access to an object */
|
||||
CO_SDO_AB_WRITEONLY = 0x06010001UL, /**< 0x06010001, Attempt to read a write only object */
|
||||
CO_SDO_AB_READONLY = 0x06010002UL, /**< 0x06010002, Attempt to write a read only object */
|
||||
CO_SDO_AB_NOT_EXIST = 0x06020000UL, /**< 0x06020000, Object does not exist in the object dictionary */
|
||||
CO_SDO_AB_NO_MAP = 0x06040041UL, /**< 0x06040041, Object cannot be mapped to the PDO */
|
||||
CO_SDO_AB_MAP_LEN = 0x06040042UL, /**< 0x06040042, Number and length of object to be mapped exceeds PDO
|
||||
length */
|
||||
CO_SDO_AB_PRAM_INCOMPAT = 0x06040043UL, /**< 0x06040043, General parameter incompatibility reasons */
|
||||
CO_SDO_AB_DEVICE_INCOMPAT = 0x06040047UL, /**< 0x06040047, General internal incompatibility in device */
|
||||
CO_SDO_AB_HW = 0x06060000UL, /**< 0x06060000, Access failed due to hardware error */
|
||||
CO_SDO_AB_TYPE_MISMATCH = 0x06070010UL, /**< 0x06070010, Data type does not match, length of service parameter
|
||||
does not match */
|
||||
CO_SDO_AB_DATA_LONG = 0x06070012UL, /**< 0x06070012, Data type does not match, length of service parameter
|
||||
too high */
|
||||
CO_SDO_AB_DATA_SHORT = 0x06070013UL, /**< 0x06070013, Data type does not match, length of service parameter
|
||||
too short */
|
||||
CO_SDO_AB_SUB_UNKNOWN = 0x06090011UL, /**< 0x06090011, Sub index does not exist */
|
||||
CO_SDO_AB_INVALID_VALUE = 0x06090030UL, /**< 0x06090030, Invalid value for parameter (download only). */
|
||||
CO_SDO_AB_VALUE_HIGH = 0x06090031UL, /**< 0x06090031, Value range of parameter written too high */
|
||||
CO_SDO_AB_VALUE_LOW = 0x06090032UL, /**< 0x06090032, Value range of parameter written too low */
|
||||
CO_SDO_AB_MAX_LESS_MIN = 0x06090036UL, /**< 0x06090036, Maximum value is less than minimum value. */
|
||||
CO_SDO_AB_NO_RESOURCE = 0x060A0023UL, /**< 0x060A0023, Resource not available: SDO connection */
|
||||
CO_SDO_AB_GENERAL = 0x08000000UL, /**< 0x08000000, General error */
|
||||
CO_SDO_AB_DATA_TRANSF = 0x08000020UL, /**< 0x08000020, Data cannot be transferred or stored to application */
|
||||
CO_SDO_AB_DATA_LOC_CTRL = 0x08000021UL, /**< 0x08000021, Data cannot be transferred or stored to application
|
||||
because of local control */
|
||||
CO_SDO_AB_DATA_DEV_STATE = 0x08000022UL, /**< 0x08000022, Data cannot be transferred or stored to application
|
||||
because of present device state */
|
||||
CO_SDO_AB_DATA_OD = 0x08000023UL, /**< 0x08000023, Object dictionary not present or dynamic generation
|
||||
fails */
|
||||
CO_SDO_AB_NO_DATA = 0x08000024UL /**< 0x08000024, No data available */
|
||||
} CO_SDO_abortCode_t;
|
||||
|
||||
/**
|
||||
* Return values from SDO server or client functions.
|
||||
*/
|
||||
typedef enum {
|
||||
CO_SDO_RT_waitingLocalTransfer = 6, /**< Waiting in client local transfer. */
|
||||
CO_SDO_RT_uploadDataBufferFull = 5, /**< Data buffer is full. SDO client: data must be read before next upload
|
||||
cycle begins. */
|
||||
CO_SDO_RT_transmittBufferFull = 4, /**< CAN transmit buffer is full. Waiting. */
|
||||
CO_SDO_RT_blockDownldInProgress = 3, /**< Block download is in progress. Sending train of messages. */
|
||||
CO_SDO_RT_blockUploadInProgress = 2, /**< Block upload is in progress. Receiving train of messages. SDO client: Data
|
||||
must not be read in this state. */
|
||||
CO_SDO_RT_waitingResponse = 1, /**< Waiting server or client response. */
|
||||
CO_SDO_RT_ok_communicationEnd = 0, /**< Success, end of communication. SDO client: uploaded data must be read. */
|
||||
CO_SDO_RT_wrongArguments = -2, /**< Error in arguments */
|
||||
CO_SDO_RT_endedWithClientAbort = -9, /**< Communication ended with client abort */
|
||||
CO_SDO_RT_endedWithServerAbort = -10, /**< Communication ended with server abort */
|
||||
} CO_SDO_return_t;
|
||||
|
||||
/**
|
||||
* SDO server object.
|
||||
*/
|
||||
typedef struct {
|
||||
CO_CANmodule_t* CANdevTx; /**< From CO_SDOserver_init() */
|
||||
CO_CANtx_t* CANtxBuff; /**< CAN transmit buffer inside CANdevTx for CAN tx message */
|
||||
OD_t* OD; /**< From CO_SDOserver_init() */
|
||||
uint8_t nodeId; /**< From CO_SDOserver_init() */
|
||||
bool_t valid; /**< If true, SDO channel is valid */
|
||||
volatile CO_SDO_state_t state; /**< Internal state of the SDO server */
|
||||
OD_IO_t OD_IO; /**< Object dictionary interface for current object. */
|
||||
uint16_t index; /**< Index of the current object in Object Dictionary */
|
||||
uint8_t subIndex; /**< Subindex of the current object in Object Dictionary */
|
||||
volatile void* CANrxNew; /**< Indicates, if new SDO message received from CAN bus. It is not cleared,
|
||||
until received message is completely processed. */
|
||||
uint8_t CANrxData[8]; /**< 8 data bytes of the received message */
|
||||
#if (((CO_CONFIG_SDO_SRV)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0) || defined CO_DOXYGEN
|
||||
CO_CANmodule_t* CANdevRx; /**< From CO_SDOserver_init() */
|
||||
uint16_t CANdevRxIdx; /**< From CO_SDOserver_init() */
|
||||
uint16_t CANdevTxIdx; /**< From CO_SDOserver_init() */
|
||||
uint32_t COB_IDClientToServer; /**< Copy of CANopen COB_ID Client -> Server, meaning of the specific bits:
|
||||
- Bit 0...10: 11-bit CAN identifier.
|
||||
- Bit 11..30: reserved, must be 0.
|
||||
- Bit 31: if 1, SDO client object is not used. */
|
||||
uint32_t COB_IDServerToClient; /**< Copy of CANopen COB_ID Server -> Client, similar as above */
|
||||
OD_extension_t OD_1200_extension; /**< Extension for OD object */
|
||||
#endif
|
||||
#if (((CO_CONFIG_SDO_SRV)&CO_CONFIG_SDO_SRV_SEGMENTED) != 0) || defined CO_DOXYGEN
|
||||
OD_size_t sizeInd; /**< Size of data, which will be transferred. It is optionally indicated by client
|
||||
in case of download or by server in case of upload. */
|
||||
OD_size_t sizeTran; /**< Size of data which is actually transferred. */
|
||||
uint8_t toggle; /**< Toggle bit toggled in each segment in segmented transfer */
|
||||
bool_t finished; /**< If true, then: data transfer is finished (by download) or read from OD variable
|
||||
is finished (by upload) */
|
||||
uint32_t SDOtimeoutTime_us; /**< Maximum timeout time between request and response in microseconds */
|
||||
uint32_t timeoutTimer; /**< Timeout timer for SDO communication */
|
||||
uint8_t buf[CO_CONFIG_SDO_SRV_BUFFER_SIZE + 1U]; /**< Interim data buffer for segmented or
|
||||
block transfer + byte for '\0' */
|
||||
OD_size_t bufOffsetWr; /**< Offset of next free data byte available for write in the buffer. */
|
||||
OD_size_t bufOffsetRd; /**< Offset of first data available for read in the buffer */
|
||||
#endif
|
||||
#if (((CO_CONFIG_SDO_SRV)&CO_CONFIG_SDO_SRV_BLOCK) != 0) || defined CO_DOXYGEN
|
||||
uint32_t block_SDOtimeoutTime_us; /**< Timeout time for SDO sub-block download, half of #SDOtimeoutTime_us */
|
||||
uint32_t block_timeoutTimer; /**< Timeout timer for SDO sub-block download */
|
||||
uint8_t block_seqno; /**< Sequence number of segment in block, 1..127 */
|
||||
uint8_t block_blksize; /**< Number of segments per block, 1..127 */
|
||||
uint8_t block_noData; /**< Number of bytes in last segment that do not contain data */
|
||||
bool_t block_crcEnabled; /**< Client CRC support in block transfer */
|
||||
uint16_t block_crc; /**< Calculated CRC checksum */
|
||||
#endif
|
||||
#if (((CO_CONFIG_SDO_SRV)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
void (*pFunctSignalPre)(void* object); /**< From CO_SDOserver_initCallbackPre() or NULL */
|
||||
void* functSignalObjectPre; /**< From CO_SDOserver_initCallbackPre() or NULL */
|
||||
#endif
|
||||
} CO_SDOserver_t;
|
||||
|
||||
/**
|
||||
* Initialize SDO object.
|
||||
*
|
||||
* Function must be called in the communication reset section.
|
||||
*
|
||||
* @param SDO This object will be initialized.
|
||||
* @param OD Object Dictionary.
|
||||
* @param OD_1200_SDOsrvPar OD entry for SDO server parameter (0x1200+), can be NULL for default single SDO server and
|
||||
* must not be NULL for additional SDO servers. With additional SDO servers it may also have IO extension enabled, to
|
||||
* allow dynamic configuration (see also @ref CO_CONFIG_FLAG_OD_DYNAMIC).
|
||||
* @param nodeId If this is first SDO channel, then "nodeId" is CANopen Node ID of this device. In all additional
|
||||
* channels "nodeId" is ignored.
|
||||
* @param SDOtimeoutTime_ms Timeout time for SDO communication in milliseconds.
|
||||
* @param CANdevRx CAN device for SDO server reception.
|
||||
* @param CANdevRxIdx Index of receive buffer in the above CAN device.
|
||||
* @param CANdevTx CAN device for SDO server transmission.
|
||||
* @param CANdevTxIdx Index of transmit buffer in the above CAN device.
|
||||
* @param [out] errInfo Additional information in case of error, may be NULL.
|
||||
*
|
||||
* @return @ref CO_ReturnError_t CO_ERROR_NO in case of success.
|
||||
*/
|
||||
CO_ReturnError_t CO_SDOserver_init(CO_SDOserver_t* SDO, OD_t* OD, OD_entry_t* OD_1200_SDOsrvPar, uint8_t nodeId,
|
||||
uint16_t SDOtimeoutTime_ms, CO_CANmodule_t* CANdevRx, uint16_t CANdevRxIdx,
|
||||
CO_CANmodule_t* CANdevTx, uint16_t CANdevTxIdx, uint32_t* errInfo);
|
||||
|
||||
#if (((CO_CONFIG_SDO_SRV)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Initialize SDOrx callback function.
|
||||
*
|
||||
* Function initializes optional callback function, which should immediately start processing of CO_SDOserver_process()
|
||||
* function. Callback is called after SDOserver message is received from the CAN bus or when new call without delay is
|
||||
* necessary (SDO block transfer is in progress).
|
||||
*
|
||||
* @param SDO This object.
|
||||
* @param object Pointer to object, which will be passed to pFunctSignalPre(). Can be NULL
|
||||
* @param pFunctSignalPre Pointer to the callback function. Not called if NULL.
|
||||
*/
|
||||
void CO_SDOserver_initCallbackPre(CO_SDOserver_t* SDO, void* object, void (*pFunctSignalPre)(void* object));
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Process SDO communication.
|
||||
*
|
||||
* Function must be called cyclically.
|
||||
*
|
||||
* @param SDO This object.
|
||||
* @param NMTisPreOrOperational True if #CO_NMT_internalState_t is NMT_PRE_OPERATIONAL or NMT_OPERATIONAL.
|
||||
* @param timeDifference_us Time difference from previous function call in [microseconds].
|
||||
* @param [out] timerNext_us info to OS - see CO_process().
|
||||
*
|
||||
* @return #CO_SDO_return_t
|
||||
*/
|
||||
CO_SDO_return_t CO_SDOserver_process(CO_SDOserver_t* SDO, bool_t NMTisPreOrOperational, uint32_t timeDifference_us,
|
||||
uint32_t* timerNext_us);
|
||||
|
||||
/** @} */ /* CO_SDOserver */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CO_SDO_SERVER_H */
|
||||
411
Middleware/CANopenNode/301/CO_SYNC.c
Normal file
411
Middleware/CANopenNode/301/CO_SYNC.c
Normal file
@@ -0,0 +1,411 @@
|
||||
/*
|
||||
* CANopen SYNC object.
|
||||
*
|
||||
* @file CO_SYNC.c
|
||||
* @ingroup CO_SYNC
|
||||
* @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 "301/CO_SYNC.h"
|
||||
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_ENABLE) != 0
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
static void
|
||||
CO_SYNC_receive(void* object, void* msg) {
|
||||
CO_SYNC_t* SYNC = object;
|
||||
uint8_t DLC = CO_CANrxMsg_readDLC(msg);
|
||||
bool_t syncReceived = false;
|
||||
|
||||
if (SYNC->counterOverflowValue == 0U) {
|
||||
if (DLC == 0U) {
|
||||
syncReceived = true;
|
||||
} else {
|
||||
SYNC->receiveError = DLC | 0x40U;
|
||||
}
|
||||
} else {
|
||||
if (DLC == 1U) {
|
||||
const uint8_t* data = CO_CANrxMsg_readData(msg);
|
||||
SYNC->counter = data[0];
|
||||
syncReceived = true;
|
||||
} else {
|
||||
SYNC->receiveError = DLC | 0x80U;
|
||||
}
|
||||
}
|
||||
|
||||
if (syncReceived) {
|
||||
/* toggle PDO receive buffer */
|
||||
SYNC->CANrxToggle = SYNC->CANrxToggle ? false : true;
|
||||
|
||||
CO_FLAG_SET(SYNC->CANrxNew);
|
||||
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0
|
||||
/* Optional signal to RTOS, which can resume task, which handles SYNC. */
|
||||
if (SYNC->pFunctSignalPre != NULL) {
|
||||
SYNC->pFunctSignalPre(SYNC->functSignalObjectPre);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0
|
||||
/*
|
||||
* Custom function for writing OD object "COB-ID sync message"
|
||||
*
|
||||
* For more information see file CO_ODinterface.h, OD_IO_t.
|
||||
*/
|
||||
static ODR_t
|
||||
OD_write_1005(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
|
||||
if ((stream == NULL) || (stream->subIndex != 0U) || (buf == NULL) || (count != sizeof(uint32_t))
|
||||
|| (countWritten == NULL)) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
CO_SYNC_t* SYNC = stream->object;
|
||||
uint32_t cobIdSync = CO_getUint32(buf);
|
||||
uint16_t CAN_ID = (uint16_t)(cobIdSync & 0x7FFU);
|
||||
|
||||
/* verify written value */
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_PRODUCER) != 0
|
||||
bool_t isProducer = (cobIdSync & 0x40000000U) != 0U;
|
||||
if (((cobIdSync & 0xBFFFF800U) != 0U) || CO_IS_RESTRICTED_CAN_ID(CAN_ID)
|
||||
|| (SYNC->isProducer && isProducer && (CAN_ID != SYNC->CAN_ID))) {
|
||||
return ODR_INVALID_VALUE;
|
||||
}
|
||||
#else
|
||||
if (((cobIdSync & 0xFFFFF800U) != 0U) || CO_IS_RESTRICTED_CAN_ID(CAN_ID)) {
|
||||
return ODR_INVALID_VALUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Configure CAN receive and transmit buffers */
|
||||
if (CAN_ID != SYNC->CAN_ID) {
|
||||
CO_ReturnError_t CANret = CO_CANrxBufferInit(SYNC->CANdevRx, SYNC->CANdevRxIdx, CAN_ID, 0x7FF, false,
|
||||
(void*)SYNC, CO_SYNC_receive);
|
||||
|
||||
if (CANret != CO_ERROR_NO) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_PRODUCER) != 0
|
||||
SYNC->CANtxBuff = CO_CANtxBufferInit(SYNC->CANdevTx, SYNC->CANdevTxIdx, CAN_ID, false,
|
||||
(SYNC->counterOverflowValue != 0U) ? 1U : 0U, false);
|
||||
|
||||
if (SYNC->CANtxBuff == NULL) {
|
||||
SYNC->isProducer = false;
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
#endif
|
||||
|
||||
SYNC->CAN_ID = CAN_ID;
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_PRODUCER) != 0
|
||||
SYNC->isProducer = isProducer;
|
||||
if (isProducer) {
|
||||
SYNC->counter = 0;
|
||||
SYNC->timer = 0;
|
||||
}
|
||||
#endif /* CO_CONFIG_SYNC) & CO_CONFIG_SYNC_PRODUCER */
|
||||
|
||||
/* write value to the original location in the Object Dictionary */
|
||||
return OD_writeOriginal(stream, buf, count, countWritten);
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_PRODUCER) != 0
|
||||
/*
|
||||
* Custom function for writing OD object "Synchronous counter overflow value"
|
||||
*
|
||||
* For more information see file CO_ODinterface.h, OD_IO_t.
|
||||
*/
|
||||
static ODR_t
|
||||
OD_write_1019(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
|
||||
if ((stream == NULL) || (stream->subIndex != 0U) || (buf == NULL) || (count != sizeof(uint8_t))
|
||||
|| (countWritten == NULL)) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
CO_SYNC_t* SYNC = stream->object;
|
||||
uint8_t syncCounterOvf = CO_getUint8(buf);
|
||||
|
||||
/* verify written value */
|
||||
if ((syncCounterOvf == 1U) || (syncCounterOvf > 240U)) {
|
||||
return ODR_INVALID_VALUE;
|
||||
}
|
||||
if (*SYNC->OD_1006_period != 0U) {
|
||||
return ODR_DATA_DEV_STATE;
|
||||
}
|
||||
|
||||
/* Configure CAN transmit buffer */
|
||||
SYNC->CANtxBuff = CO_CANtxBufferInit(SYNC->CANdevTx, SYNC->CANdevTxIdx, SYNC->CAN_ID, false,
|
||||
(syncCounterOvf != 0U) ? 1U : 0U, false);
|
||||
|
||||
if (SYNC->CANtxBuff == NULL) {
|
||||
SYNC->isProducer = false;
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
SYNC->counterOverflowValue = syncCounterOvf;
|
||||
|
||||
/* write value to the original location in the Object Dictionary */
|
||||
return OD_writeOriginal(stream, buf, count, countWritten);
|
||||
}
|
||||
#endif /* (CO_CONFIG_SYNC) & CO_CONFIG_SYNC_PRODUCER */
|
||||
#endif /* (CO_CONFIG_SYNC) & CO_CONFIG_FLAG_OD_DYNAMIC */
|
||||
|
||||
CO_ReturnError_t
|
||||
CO_SYNC_init(CO_SYNC_t* SYNC, CO_EM_t* em, OD_entry_t* OD_1005_cobIdSync, OD_entry_t* OD_1006_commCyclePeriod,
|
||||
OD_entry_t* OD_1007_syncWindowLen, OD_entry_t* OD_1019_syncCounterOvf, CO_CANmodule_t* CANdevRx,
|
||||
uint16_t CANdevRxIdx,
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_PRODUCER) != 0
|
||||
CO_CANmodule_t* CANdevTx, uint16_t CANdevTxIdx,
|
||||
#endif
|
||||
uint32_t* errInfo) {
|
||||
ODR_t odRet;
|
||||
|
||||
/* verify arguments */
|
||||
if ((SYNC == NULL) || (em == NULL) || (OD_1005_cobIdSync == NULL)
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_PRODUCER) != 0
|
||||
|| (OD_1006_commCyclePeriod == NULL) || (CANdevTx == NULL)
|
||||
#endif
|
||||
|| (CANdevRx == NULL)) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* clear object */
|
||||
(void)memset(SYNC, 0, sizeof(CO_SYNC_t));
|
||||
|
||||
/* get and verify "COB-ID SYNC message" from OD and configure extension */
|
||||
uint32_t cobIdSync = 0x00000080;
|
||||
|
||||
odRet = OD_get_u32(OD_1005_cobIdSync, 0, &cobIdSync, true);
|
||||
if (odRet != ODR_OK) {
|
||||
if (errInfo != NULL) {
|
||||
*errInfo = OD_getIndex(OD_1005_cobIdSync);
|
||||
}
|
||||
return CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0
|
||||
SYNC->OD_1005_extension.object = SYNC;
|
||||
SYNC->OD_1005_extension.read = OD_readOriginal;
|
||||
SYNC->OD_1005_extension.write = OD_write_1005;
|
||||
(void)OD_extension_init(OD_1005_cobIdSync, &SYNC->OD_1005_extension);
|
||||
#endif
|
||||
|
||||
/* get and verify "Communication cycle period" from OD */
|
||||
SYNC->OD_1006_period = OD_getPtr(OD_1006_commCyclePeriod, 0, sizeof(uint32_t), NULL);
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_PRODUCER) != 0
|
||||
if (SYNC->OD_1006_period == NULL) {
|
||||
if (errInfo != NULL) {
|
||||
*errInfo = OD_getIndex(OD_1006_commCyclePeriod);
|
||||
}
|
||||
return CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
#else
|
||||
if ((OD_1006_commCyclePeriod != NULL) && (SYNC->OD_1006_period == NULL)) {
|
||||
if (errInfo != NULL) {
|
||||
*errInfo = OD_getIndex(OD_1006_commCyclePeriod);
|
||||
}
|
||||
return CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* get "Synchronous window length" from OD (optional parameter) */
|
||||
SYNC->OD_1007_window = OD_getPtr(OD_1007_syncWindowLen, 0, sizeof(uint32_t), NULL);
|
||||
if ((OD_1007_syncWindowLen != NULL) && (SYNC->OD_1007_window == NULL)) {
|
||||
if (errInfo != NULL) {
|
||||
*errInfo = OD_getIndex(OD_1007_syncWindowLen);
|
||||
}
|
||||
return CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
|
||||
/* get and verify optional "Synchronous counter overflow value" from OD and configure extension */
|
||||
uint8_t syncCounterOvf = 0;
|
||||
|
||||
if (OD_1019_syncCounterOvf != NULL) {
|
||||
odRet = OD_get_u8(OD_1019_syncCounterOvf, 0, &syncCounterOvf, true);
|
||||
if (odRet != ODR_OK) {
|
||||
if (errInfo != NULL) {
|
||||
*errInfo = OD_getIndex(OD_1019_syncCounterOvf);
|
||||
}
|
||||
return CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
if (syncCounterOvf == 1U) {
|
||||
syncCounterOvf = 2;
|
||||
} else if (syncCounterOvf > 240U) {
|
||||
syncCounterOvf = 240;
|
||||
} else { /* MISRA C 2004 14.10 */
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_PRODUCER) != 0
|
||||
SYNC->OD_1019_extension.object = SYNC;
|
||||
SYNC->OD_1019_extension.read = OD_readOriginal;
|
||||
SYNC->OD_1019_extension.write = OD_write_1019;
|
||||
(void)OD_extension_init(OD_1019_syncCounterOvf, &SYNC->OD_1019_extension);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
SYNC->counterOverflowValue = syncCounterOvf;
|
||||
|
||||
/* Configure object variables */
|
||||
SYNC->em = em;
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_PRODUCER) != 0
|
||||
SYNC->isProducer = (cobIdSync & 0x40000000U) != 0U;
|
||||
SYNC->CANdevTx = CANdevTx;
|
||||
#endif
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0
|
||||
SYNC->CAN_ID = (uint16_t)(cobIdSync & 0x7FFU);
|
||||
SYNC->CANdevRx = CANdevRx;
|
||||
SYNC->CANdevRxIdx = CANdevRxIdx;
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_PRODUCER) != 0
|
||||
SYNC->CANdevTxIdx = CANdevTxIdx;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* configure SYNC CAN reception and transmission */
|
||||
CO_ReturnError_t ret = CO_CANrxBufferInit(CANdevRx, CANdevRxIdx, (uint16_t)(cobIdSync & 0x7FFU), 0x7FF, false,
|
||||
(void*)SYNC, CO_SYNC_receive);
|
||||
if (ret != CO_ERROR_NO) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_PRODUCER) != 0
|
||||
SYNC->CANtxBuff = CO_CANtxBufferInit(CANdevTx, CANdevTxIdx, (uint16_t)(cobIdSync & 0x7FFU), false,
|
||||
(syncCounterOvf != 0U) ? 1U : 0U, false);
|
||||
|
||||
if (SYNC->CANtxBuff == NULL) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
#endif
|
||||
|
||||
return CO_ERROR_NO;
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0
|
||||
void
|
||||
CO_SYNC_initCallbackPre(CO_SYNC_t* SYNC, void* object, void (*pFunctSignalPre)(void* object)) {
|
||||
if (SYNC != NULL) {
|
||||
SYNC->functSignalObjectPre = object;
|
||||
SYNC->pFunctSignalPre = pFunctSignalPre;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
CO_SYNC_status_t
|
||||
CO_SYNC_process(CO_SYNC_t* SYNC, bool_t NMTisPreOrOperational, uint32_t timeDifference_us, uint32_t* timerNext_us) {
|
||||
(void)timerNext_us; /* may be unused */
|
||||
|
||||
CO_SYNC_status_t syncStatus = CO_SYNC_NONE;
|
||||
|
||||
if (NMTisPreOrOperational) {
|
||||
/* update sync timer, no overflow */
|
||||
uint32_t timerNew = SYNC->timer + timeDifference_us;
|
||||
if (timerNew > SYNC->timer) {
|
||||
SYNC->timer = timerNew;
|
||||
}
|
||||
|
||||
/* was SYNC just received */
|
||||
if (CO_FLAG_READ(SYNC->CANrxNew)) {
|
||||
SYNC->timer = 0;
|
||||
syncStatus = CO_SYNC_RX_TX;
|
||||
CO_FLAG_CLEAR(SYNC->CANrxNew);
|
||||
}
|
||||
|
||||
uint32_t OD_1006_period = (SYNC->OD_1006_period != NULL) ? *SYNC->OD_1006_period : 0U;
|
||||
|
||||
if (OD_1006_period > 0U) {
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_PRODUCER) != 0
|
||||
if (SYNC->isProducer) {
|
||||
if (SYNC->timer >= OD_1006_period) {
|
||||
syncStatus = CO_SYNC_RX_TX;
|
||||
(void)CO_SYNCsend(SYNC);
|
||||
}
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_FLAG_TIMERNEXT) != 0
|
||||
/* Calculate when next SYNC needs to be sent */
|
||||
if (timerNext_us != NULL) {
|
||||
uint32_t diff = OD_1006_period - SYNC->timer;
|
||||
if (*timerNext_us > diff) {
|
||||
*timerNext_us = diff;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else
|
||||
#endif /* (CO_CONFIG_SYNC) & CO_CONFIG_SYNC_PRODUCER */
|
||||
|
||||
/* Verify timeout of SYNC */
|
||||
if (SYNC->timeoutError == 1U) {
|
||||
/* periodTimeout is 1,5 * OD_1006_period, no overflow */
|
||||
uint32_t periodTimeout = OD_1006_period + (OD_1006_period >> 1);
|
||||
if (periodTimeout < OD_1006_period) {
|
||||
periodTimeout = 0xFFFFFFFFU;
|
||||
}
|
||||
|
||||
if (SYNC->timer > periodTimeout) {
|
||||
CO_errorReport(SYNC->em, CO_EM_SYNC_TIME_OUT, CO_EMC_COMMUNICATION, SYNC->timer);
|
||||
SYNC->timeoutError = 2;
|
||||
}
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_FLAG_TIMERNEXT) != 0
|
||||
else if (timerNext_us != NULL) {
|
||||
uint32_t diff = periodTimeout - SYNC->timer;
|
||||
if (*timerNext_us > diff) {
|
||||
*timerNext_us = diff;
|
||||
}
|
||||
} else { /* MISRA C 2004 14.10 */
|
||||
}
|
||||
#endif
|
||||
} else { /* MISRA C 2004 14.10 */
|
||||
}
|
||||
} /* if (OD_1006_period > 0) */
|
||||
|
||||
/* Synchronous PDOs are allowed only inside time window */
|
||||
if ((SYNC->OD_1007_window != NULL) && (*SYNC->OD_1007_window > 0U) && (SYNC->timer > *SYNC->OD_1007_window)) {
|
||||
if (!SYNC->syncIsOutsideWindow) {
|
||||
syncStatus = CO_SYNC_PASSED_WINDOW;
|
||||
}
|
||||
SYNC->syncIsOutsideWindow = true;
|
||||
} else {
|
||||
SYNC->syncIsOutsideWindow = false;
|
||||
}
|
||||
|
||||
/* verify error from receive function */
|
||||
if (SYNC->receiveError != 0U) {
|
||||
CO_errorReport(SYNC->em, CO_EM_SYNC_LENGTH, CO_EMC_SYNC_DATA_LENGTH, SYNC->receiveError);
|
||||
SYNC->receiveError = 0;
|
||||
}
|
||||
} /* if (NMTisPreOrOperational) */
|
||||
else {
|
||||
CO_FLAG_CLEAR(SYNC->CANrxNew);
|
||||
SYNC->receiveError = 0;
|
||||
SYNC->counter = 0;
|
||||
SYNC->timer = 0;
|
||||
}
|
||||
|
||||
if (syncStatus == CO_SYNC_RX_TX) {
|
||||
if (SYNC->timeoutError == 2U) {
|
||||
CO_errorReset(SYNC->em, CO_EM_SYNC_TIME_OUT, 0);
|
||||
}
|
||||
SYNC->timeoutError = 1;
|
||||
}
|
||||
|
||||
return syncStatus;
|
||||
}
|
||||
|
||||
#endif /* (CO_CONFIG_SYNC) & CO_CONFIG_SYNC_ENABLE */
|
||||
205
Middleware/CANopenNode/301/CO_SYNC.h
Normal file
205
Middleware/CANopenNode/301/CO_SYNC.h
Normal file
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* CANopen Synchronisation protocol.
|
||||
*
|
||||
* @file CO_SYNC.h
|
||||
* @ingroup CO_SYNC
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2004 - 2020 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.
|
||||
*/
|
||||
|
||||
#ifndef CO_SYNC_H
|
||||
#define CO_SYNC_H
|
||||
|
||||
#include "301/CO_driver.h"
|
||||
#include "301/CO_ODinterface.h"
|
||||
#include "301/CO_Emergency.h"
|
||||
|
||||
/* default configuration, see CO_config.h */
|
||||
#ifndef CO_CONFIG_SYNC
|
||||
#define CO_CONFIG_SYNC \
|
||||
(CO_CONFIG_SYNC_ENABLE | CO_CONFIG_SYNC_PRODUCER | CO_CONFIG_GLOBAL_RT_FLAG_CALLBACK_PRE \
|
||||
| CO_CONFIG_GLOBAL_FLAG_TIMERNEXT | CO_CONFIG_GLOBAL_FLAG_OD_DYNAMIC)
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_SYNC SYNC
|
||||
* CANopen Synchronisation protocol.
|
||||
*
|
||||
* @ingroup CO_CANopen_301
|
||||
* @{
|
||||
* For CAN identifier see @ref CO_Default_CAN_ID_t
|
||||
*
|
||||
* SYNC message is used for synchronization of the nodes on network. One node can be SYNC producer, others can be SYNC
|
||||
* consumers. Synchronous TPDOs are transmitted after the CANopen SYNC message. Synchronous received PDOs are
|
||||
* accepted(copied to OD) immediatelly after the reception of the next SYNC message.
|
||||
*
|
||||
* ####Contents of SYNC message
|
||||
* By default SYNC message has no data. If _Synchronous counter overflow value_ from Object dictionary (index 0x1019) is
|
||||
* different than 0, SYNC message has one data byte: _counter_ incremented by 1 with every SYNC transmission.
|
||||
*
|
||||
* ####SYNC in CANopenNode
|
||||
* According to CANopen, synchronous RPDOs must be processed after reception of the next sync messsage. For that reason,
|
||||
* there is a double receive buffer for each synchronous RPDO. At the moment, when SYNC is received or transmitted,
|
||||
* internal variable CANrxToggle toggles. That variable is then used by synchronous RPDO to determine, which of the two
|
||||
* buffers is used for RPDO reception and which for RPDO processing.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SYNC producer and consumer object.
|
||||
*/
|
||||
typedef struct {
|
||||
CO_EM_t* em; /**< From CO_SYNC_init() */
|
||||
volatile void* CANrxNew; /**< Indicates, if new SYNC message received from CAN bus */
|
||||
uint8_t receiveError; /**< Set to nonzero value, if SYNC with wrong data length is received */
|
||||
bool_t CANrxToggle; /**< Variable toggles, if new SYNC message received from CAN bus */
|
||||
uint8_t timeoutError; /**< Sync timeout monitoring: 0 = not started; 1 = started; 2 = sync timeout error state */
|
||||
uint8_t counterOverflowValue; /**< Value from _Synchronous counter overflow value_ variable from Object dictionary
|
||||
(index 0x1019) */
|
||||
uint8_t counter; /**< Counter of the SYNC message if counterOverflowValue is different than zero */
|
||||
bool_t syncIsOutsideWindow; /**< True, if current time is outside "synchronous window" (OD 1007) */
|
||||
uint32_t timer; /**< Timer for the SYNC message in [microseconds]. Set to zero after received or
|
||||
transmitted SYNC message */
|
||||
uint32_t* OD_1006_period; /**< Pointer to variable in OD, "Communication cycle period" in microseconds */
|
||||
uint32_t* OD_1007_window; /**< Pointer to variable in OD, "Synchronous window length" in microseconds */
|
||||
|
||||
#if (((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_PRODUCER) != 0) || defined CO_DOXYGEN
|
||||
bool_t isProducer; /**< True, if device is SYNC producer. Calculated from _COB ID SYNC Message_ variable
|
||||
from Object dictionary(index 0x1005).*/
|
||||
CO_CANmodule_t* CANdevTx; /**< From CO_SYNC_init() */
|
||||
CO_CANtx_t* CANtxBuff; /**< CAN transmit buffer inside CANdevTx */
|
||||
#endif
|
||||
|
||||
#if ((CO_CONFIG_SYNC)&CO_CONFIG_FLAG_OD_DYNAMIC) || defined CO_DOXYGEN
|
||||
CO_CANmodule_t* CANdevRx; /**< From CO_SYNC_init() */
|
||||
uint16_t CANdevRxIdx; /**< From CO_SYNC_init() */
|
||||
OD_extension_t OD_1005_extension; /**< Extension for OD object */
|
||||
uint16_t CAN_ID; /**< CAN ID of the SYNC message. Calculated from _COB ID SYNC Message_ variable
|
||||
from Object dictionary (index 0x1005). */
|
||||
#if (((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_PRODUCER) != 0) || defined CO_DOXYGEN
|
||||
uint16_t CANdevTxIdx; /**< From CO_SYNC_init() */
|
||||
OD_extension_t OD_1019_extension; /**< Extension for OD object */
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_SYNC)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
void (*pFunctSignalPre)(void* object); /**< From CO_SYNC_initCallbackPre() or NULL */
|
||||
void* functSignalObjectPre; /**< From CO_SYNC_initCallbackPre() or NULL */
|
||||
#endif
|
||||
} CO_SYNC_t;
|
||||
|
||||
/**
|
||||
* Return value for @ref CO_SYNC_process
|
||||
*/
|
||||
typedef enum {
|
||||
CO_SYNC_NONE = 0, /**< No SYNC event in last cycle */
|
||||
CO_SYNC_RX_TX = 1, /**< SYNC message was received or transmitted in last cycle */
|
||||
CO_SYNC_PASSED_WINDOW = 2 /**< Time has just passed SYNC window (OD_1007) in last cycle */
|
||||
} CO_SYNC_status_t;
|
||||
|
||||
/**
|
||||
* Initialize SYNC object.
|
||||
*
|
||||
* Function must be called in the communication reset section.
|
||||
*
|
||||
* @param SYNC This object will be initialized.
|
||||
* @param em Emergency object.
|
||||
* @param OD_1005_cobIdSync OD entry for 0x1005 - "COB-ID SYNC message", entry is required.
|
||||
* @param OD_1006_commCyclePeriod OD entry for 0x1006 - "Communication cycle period", entry is required if device is
|
||||
* sync producer.
|
||||
* @param OD_1007_syncWindowLen OD entry for 0x1007 - "Synchronous window length", entry is optional, may be NULL.
|
||||
* @param OD_1019_syncCounterOvf OD entry for 0x1019 - "Synchronous counter overflow value", entry is optional, may be
|
||||
* NULL.
|
||||
* @param CANdevRx CAN device for SYNC reception.
|
||||
* @param CANdevRxIdx Index of receive buffer in the above CAN device.
|
||||
* @param CANdevTx CAN device for SYNC transmission.
|
||||
* @param CANdevTxIdx Index of transmit buffer in the above CAN device.
|
||||
* @param [out] errInfo Additional information in case of error, may be NULL.
|
||||
*
|
||||
* @return #CO_ReturnError_t CO_ERROR_NO on success.
|
||||
*/
|
||||
CO_ReturnError_t CO_SYNC_init(CO_SYNC_t* SYNC, CO_EM_t* em, OD_entry_t* OD_1005_cobIdSync,
|
||||
OD_entry_t* OD_1006_commCyclePeriod, OD_entry_t* OD_1007_syncWindowLen,
|
||||
OD_entry_t* OD_1019_syncCounterOvf, CO_CANmodule_t* CANdevRx, uint16_t CANdevRxIdx,
|
||||
#if (((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_PRODUCER) != 0) || defined CO_DOXYGEN
|
||||
CO_CANmodule_t* CANdevTx, uint16_t CANdevTxIdx,
|
||||
#endif
|
||||
uint32_t* errInfo);
|
||||
|
||||
#if (((CO_CONFIG_SYNC)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Initialize SYNC callback function.
|
||||
*
|
||||
* Function initializes optional callback function, which should immediately start processing of CO_SYNC_process()
|
||||
* function. Callback is called after SYNC message is received from the CAN bus.
|
||||
*
|
||||
* @param SYNC This object.
|
||||
* @param object Pointer to object, which will be passed to pFunctSignalPre().
|
||||
* @param pFunctSignalPre Pointer to the callback function. Not called if NULL.
|
||||
*/
|
||||
void CO_SYNC_initCallbackPre(CO_SYNC_t* SYNC, void* object, void (*pFunctSignalPre)(void* object));
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_PRODUCER) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Send SYNC message.
|
||||
*
|
||||
* This function prepares and sends a SYNC object. The application should only call this if direct control of SYNC
|
||||
* transmission is needed, otherwise use CO_SYNC_process().
|
||||
*
|
||||
* @param SYNC SYNC object.
|
||||
*
|
||||
* @return Same as CO_CANsend().
|
||||
*/
|
||||
static inline CO_ReturnError_t
|
||||
CO_SYNCsend(CO_SYNC_t* SYNC) {
|
||||
if (++SYNC->counter > SYNC->counterOverflowValue) {
|
||||
SYNC->counter = 1;
|
||||
}
|
||||
SYNC->timer = 0;
|
||||
SYNC->CANrxToggle = SYNC->CANrxToggle ? false : true;
|
||||
SYNC->CANtxBuff->data[0] = SYNC->counter;
|
||||
return CO_CANsend(SYNC->CANdevTx, SYNC->CANtxBuff);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Process SYNC communication.
|
||||
*
|
||||
* Function must be called cyclically.
|
||||
*
|
||||
* @param SYNC This object.
|
||||
* @param NMTisPreOrOperational True if this node is NMT_PRE_OPERATIONAL or NMT_OPERATIONAL state.
|
||||
* @param timeDifference_us Time difference from previous function call in [microseconds].
|
||||
* @param [out] timerNext_us info to OS - see CO_process().
|
||||
*
|
||||
* @return @ref CO_SYNC_status_t
|
||||
*/
|
||||
CO_SYNC_status_t CO_SYNC_process(CO_SYNC_t* SYNC, bool_t NMTisPreOrOperational, uint32_t timeDifference_us,
|
||||
uint32_t* timerNext_us);
|
||||
|
||||
/** @} */ /* CO_SYNC */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_SYNC) & CO_CONFIG_SYNC_ENABLE */
|
||||
|
||||
#endif /* CO_SYNC_H */
|
||||
208
Middleware/CANopenNode/301/CO_TIME.c
Normal file
208
Middleware/CANopenNode/301/CO_TIME.c
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* CANopen TIME object.
|
||||
*
|
||||
* @file CO_TIME.c
|
||||
* @ingroup CO_TIME
|
||||
* @author Julien PEYREGNE
|
||||
* @copyright 2019 - 2020 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_TIME.h"
|
||||
|
||||
#if ((CO_CONFIG_TIME)&CO_CONFIG_TIME_ENABLE) != 0
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
static void
|
||||
CO_TIME_receive(void* object, void* msg) {
|
||||
CO_TIME_t* TIME = object;
|
||||
uint8_t DLC = CO_CANrxMsg_readDLC(msg);
|
||||
const uint8_t* data = CO_CANrxMsg_readData(msg);
|
||||
|
||||
if (DLC == CO_TIME_MSG_LENGTH) {
|
||||
(void)memcpy(TIME->timeStamp, data, sizeof(TIME->timeStamp));
|
||||
CO_FLAG_SET(TIME->CANrxNew);
|
||||
|
||||
#if ((CO_CONFIG_TIME)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0
|
||||
/* Optional signal to RTOS, which can resume task, which handles TIME. */
|
||||
if (TIME->pFunctSignalPre != NULL) {
|
||||
TIME->pFunctSignalPre(TIME->functSignalObjectPre);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_TIME)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0
|
||||
/*
|
||||
* Custom function for writing OD object "COB-ID time stamp"
|
||||
*
|
||||
* For more information see file CO_ODinterface.h, OD_IO_t.
|
||||
*/
|
||||
static ODR_t
|
||||
OD_write_1012(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
|
||||
if ((stream == NULL) || (stream->subIndex != 0U) || (buf == NULL) || (count != sizeof(uint32_t))
|
||||
|| (countWritten == NULL)) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
CO_TIME_t* TIME = stream->object;
|
||||
|
||||
/* verify written value */
|
||||
uint32_t cobIdTimeStamp = CO_getUint32(buf);
|
||||
uint16_t CAN_ID = (uint16_t)(cobIdTimeStamp & 0x7FFU);
|
||||
if (((cobIdTimeStamp & 0x3FFFF800U) != 0U) || CO_IS_RESTRICTED_CAN_ID(CAN_ID)) {
|
||||
return ODR_INVALID_VALUE;
|
||||
}
|
||||
|
||||
/* update object */
|
||||
TIME->isConsumer = (cobIdTimeStamp & 0x80000000UL) != 0U;
|
||||
TIME->isProducer = (cobIdTimeStamp & 0x40000000UL) != 0U;
|
||||
|
||||
/* write value to the original location in the Object Dictionary */
|
||||
return OD_writeOriginal(stream, buf, count, countWritten);
|
||||
}
|
||||
#endif
|
||||
|
||||
CO_ReturnError_t
|
||||
CO_TIME_init(CO_TIME_t* TIME, OD_entry_t* OD_1012_cobIdTimeStamp, CO_CANmodule_t* CANdevRx, uint16_t CANdevRxIdx,
|
||||
#if ((CO_CONFIG_TIME)&CO_CONFIG_TIME_PRODUCER) != 0
|
||||
CO_CANmodule_t* CANdevTx, uint16_t CANdevTxIdx,
|
||||
#endif
|
||||
uint32_t* errInfo) {
|
||||
/* verify arguments */
|
||||
if ((TIME == NULL) || (OD_1012_cobIdTimeStamp == NULL) || (CANdevRx == NULL)
|
||||
#if ((CO_CONFIG_TIME)&CO_CONFIG_TIME_PRODUCER) != 0
|
||||
|| CANdevTx == NULL
|
||||
#endif
|
||||
) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
(void)memset(TIME, 0, sizeof(CO_TIME_t));
|
||||
|
||||
/* get parameters from object dictionary and configure extension */
|
||||
uint32_t cobIdTimeStamp;
|
||||
ODR_t odRet = OD_get_u32(OD_1012_cobIdTimeStamp, 0, &cobIdTimeStamp, true);
|
||||
if (odRet != ODR_OK) {
|
||||
if (errInfo != NULL) {
|
||||
*errInfo = OD_getIndex(OD_1012_cobIdTimeStamp);
|
||||
}
|
||||
return CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
#if ((CO_CONFIG_TIME)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0
|
||||
TIME->OD_1012_extension.object = TIME;
|
||||
TIME->OD_1012_extension.read = OD_readOriginal;
|
||||
TIME->OD_1012_extension.write = OD_write_1012;
|
||||
(void)OD_extension_init(OD_1012_cobIdTimeStamp, &TIME->OD_1012_extension);
|
||||
#endif
|
||||
|
||||
/* Configure object variables */
|
||||
uint16_t cobId = (uint16_t)(cobIdTimeStamp & 0x7FFU);
|
||||
TIME->isConsumer = (cobIdTimeStamp & 0x80000000UL) != 0U;
|
||||
TIME->isProducer = (cobIdTimeStamp & 0x40000000UL) != 0U;
|
||||
CO_FLAG_CLEAR(TIME->CANrxNew);
|
||||
|
||||
/* configure TIME consumer message reception */
|
||||
if (TIME->isConsumer) {
|
||||
CO_ReturnError_t ret = CO_CANrxBufferInit(CANdevRx, CANdevRxIdx, cobId, 0x7FF, false, (void*)TIME,
|
||||
CO_TIME_receive);
|
||||
if (ret != CO_ERROR_NO) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_TIME)&CO_CONFIG_TIME_PRODUCER) != 0
|
||||
/* configure TIME producer message transmission */
|
||||
TIME->CANdevTx = CANdevTx;
|
||||
TIME->CANtxBuff = CO_CANtxBufferInit(CANdevTx, CANdevTxIdx, cobId, false, CO_TIME_MSG_LENGTH, false);
|
||||
|
||||
if (TIME->CANtxBuff == NULL) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
#endif
|
||||
|
||||
return CO_ERROR_NO;
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_TIME)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0
|
||||
void
|
||||
CO_TIME_initCallbackPre(CO_TIME_t* TIME, void* object, void (*pFunctSignalPre)(void* object)) {
|
||||
if (TIME != NULL) {
|
||||
TIME->functSignalObjectPre = object;
|
||||
TIME->pFunctSignalPre = pFunctSignalPre;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool_t
|
||||
CO_TIME_process(CO_TIME_t* TIME, bool_t NMTisPreOrOperational, uint32_t timeDifference_us) {
|
||||
bool_t timestampReceived = false;
|
||||
|
||||
/* Was TIME stamp message just received */
|
||||
if (NMTisPreOrOperational && TIME->isConsumer) {
|
||||
if (CO_FLAG_READ(TIME->CANrxNew)) {
|
||||
uint32_t ms_swapped = CO_getUint32(&TIME->timeStamp[0]);
|
||||
uint16_t days_swapped = CO_getUint16(&TIME->timeStamp[4]);
|
||||
TIME->ms = CO_SWAP_32(ms_swapped) & 0x0FFFFFFFU;
|
||||
TIME->days = CO_SWAP_16(days_swapped);
|
||||
TIME->residual_us = 0;
|
||||
timestampReceived = true;
|
||||
|
||||
CO_FLAG_CLEAR(TIME->CANrxNew);
|
||||
}
|
||||
} else {
|
||||
CO_FLAG_CLEAR(TIME->CANrxNew);
|
||||
}
|
||||
|
||||
/* Update time */
|
||||
uint32_t ms = 0;
|
||||
if (!timestampReceived && (timeDifference_us > 0U)) {
|
||||
uint32_t us = timeDifference_us + TIME->residual_us;
|
||||
ms = us / 1000U;
|
||||
TIME->residual_us = (uint16_t)(us % 1000U);
|
||||
TIME->ms += ms;
|
||||
if (TIME->ms >= ((uint32_t)1000U * 60U * 60U * 24U)) {
|
||||
TIME->ms -= ((uint32_t)1000U * 60U * 60U * 24U);
|
||||
TIME->days += 1U;
|
||||
}
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_TIME)&CO_CONFIG_TIME_PRODUCER) != 0
|
||||
if (NMTisPreOrOperational && TIME->isProducer && TIME->producerInterval_ms > 0) {
|
||||
if (TIME->producerTimer_ms >= TIME->producerInterval_ms) {
|
||||
TIME->producerTimer_ms -= TIME->producerInterval_ms;
|
||||
|
||||
uint32_t ms_swapped = CO_SWAP_32(TIME->ms);
|
||||
uint16_t days_swapped = CO_SWAP_16(TIME->days);
|
||||
(void)CO_setUint32(&TIME->CANtxBuff->data[0], ms_swapped);
|
||||
(void)CO_setUint16(&TIME->CANtxBuff->data[4], days_swapped);
|
||||
(void)CO_CANsend(TIME->CANdevTx, TIME->CANtxBuff);
|
||||
} else {
|
||||
TIME->producerTimer_ms += ms;
|
||||
}
|
||||
} else {
|
||||
TIME->producerTimer_ms = TIME->producerInterval_ms;
|
||||
}
|
||||
#endif
|
||||
|
||||
return timestampReceived;
|
||||
}
|
||||
|
||||
#endif /* (CO_CONFIG_TIME) & CO_CONFIG_TIME_ENABLE */
|
||||
172
Middleware/CANopenNode/301/CO_TIME.h
Normal file
172
Middleware/CANopenNode/301/CO_TIME.h
Normal file
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* CANopen Time-stamp protocol.
|
||||
*
|
||||
* @file CO_TIME.h
|
||||
* @ingroup CO_TIME
|
||||
* @author Julien PEYREGNE
|
||||
* @copyright 2019 - 2020 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.
|
||||
*/
|
||||
|
||||
#ifndef CO_TIME_H
|
||||
#define CO_TIME_H
|
||||
|
||||
#include "301/CO_driver.h"
|
||||
#include "301/CO_ODinterface.h"
|
||||
#include "301/CO_NMT_Heartbeat.h"
|
||||
|
||||
/* default configuration, see CO_config.h */
|
||||
#ifndef CO_CONFIG_TIME
|
||||
#define CO_CONFIG_TIME (CO_CONFIG_TIME_ENABLE | CO_CONFIG_GLOBAL_FLAG_CALLBACK_PRE | CO_CONFIG_GLOBAL_FLAG_OD_DYNAMIC)
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_TIME)&CO_CONFIG_TIME_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_TIME TIME
|
||||
* CANopen Time-stamp protocol.
|
||||
*
|
||||
* @ingroup CO_CANopen_301
|
||||
* @{
|
||||
* For CAN identifier see @ref CO_Default_CAN_ID_t
|
||||
*
|
||||
* TIME message is used for time synchronization of the nodes on the network. One node should be TIME producer, others
|
||||
* can be TIME consumers. This is configured by COB_ID_TIME object 0x1012:
|
||||
*
|
||||
* - bit 31 should be set for a consumer
|
||||
* - bit 30 should be set for a producer
|
||||
* - bits 0..10 is CAN-ID, 0x100 by default
|
||||
*
|
||||
* Current time can be read from @p CO_TIME_t->ms (milliseconds after midnight) and @p CO_TIME_t->days (number of days
|
||||
* since January 1, 1984). Those values are updated on each @ref CO_TIME_process() call, either from internal timer or
|
||||
* from received time stamp message.
|
||||
*
|
||||
* Current time can be set with @ref CO_TIME_set() function, which is necessary at least once, if time producer. If
|
||||
* configured, time stamp message is send from @ref CO_TIME_process() in intervals specified by @ref CO_TIME_set()
|
||||
*/
|
||||
|
||||
#define CO_TIME_MSG_LENGTH 6U /**< Length of the TIME message */
|
||||
|
||||
/**
|
||||
* TIME producer and consumer object.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t timeStamp[CO_TIME_MSG_LENGTH]; /**< Received timestamp data */
|
||||
uint32_t ms; /**< Milliseconds after midnight */
|
||||
uint16_t days; /**< Number of days since January 1, 1984 */
|
||||
uint16_t residual_us; /**< Residual microseconds calculated inside CO_TIME_process() */
|
||||
bool_t isConsumer; /**< True, if device is TIME consumer. Calculated from _COB ID TIME Message_
|
||||
variable from Object dictionary (index 0x1012). */
|
||||
bool_t isProducer; /**< True, if device is TIME producer. Calculated from _COB ID TIME Message_
|
||||
variable from Object dictionary (index 0x1012). */
|
||||
volatile void* CANrxNew; /**< Variable indicates, if new TIME message received from CAN bus */
|
||||
#if (((CO_CONFIG_TIME)&CO_CONFIG_TIME_PRODUCER) != 0) || defined CO_DOXYGEN
|
||||
uint32_t producerInterval_ms; /**< Interval for time producer in milli seconds */
|
||||
uint32_t producerTimer_ms; /**< Sync producer timer */
|
||||
CO_CANmodule_t* CANdevTx; /**< From CO_TIME_init() */
|
||||
CO_CANtx_t* CANtxBuff; /**< CAN transmit buffer */
|
||||
#endif
|
||||
#if (((CO_CONFIG_TIME)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
void (*pFunctSignalPre)(void* object); /**< From CO_TIME_initCallbackPre() or NULL */
|
||||
void* functSignalObjectPre; /**< From CO_TIME_initCallbackPre() or NULL */
|
||||
#endif
|
||||
#if (((CO_CONFIG_TIME)&CO_CONFIG_FLAG_OD_DYNAMIC) != 0) || defined CO_DOXYGEN
|
||||
OD_extension_t OD_1012_extension; /**< Extension for OD object */
|
||||
#endif
|
||||
} CO_TIME_t;
|
||||
|
||||
/**
|
||||
* Initialize TIME object.
|
||||
*
|
||||
* Function must be called in the communication reset section.
|
||||
*
|
||||
* @param TIME This object will be initialized.
|
||||
* @param OD_1012_cobIdTimeStamp OD entry for 0x1012 - "COB-ID time stamp", entry is required.
|
||||
* @param CANdevRx CAN device for TIME reception.
|
||||
* @param CANdevRxIdx Index of receive buffer in the above CAN device.
|
||||
* @param CANdevTx CAN device for TIME transmission.
|
||||
* @param CANdevTxIdx Index of transmit buffer in the above CAN device.
|
||||
* @param [out] errInfo Additional information in case of error, may be NULL.
|
||||
*
|
||||
* @return #CO_ReturnError_t CO_ERROR_NO on success.
|
||||
*/
|
||||
CO_ReturnError_t CO_TIME_init(CO_TIME_t* TIME, OD_entry_t* OD_1012_cobIdTimeStamp, CO_CANmodule_t* CANdevRx,
|
||||
uint16_t CANdevRxIdx,
|
||||
#if (((CO_CONFIG_TIME)&CO_CONFIG_TIME_PRODUCER) != 0) || defined CO_DOXYGEN
|
||||
CO_CANmodule_t* CANdevTx, uint16_t CANdevTxIdx,
|
||||
#endif
|
||||
uint32_t* errInfo);
|
||||
|
||||
#if (((CO_CONFIG_TIME)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Initialize TIME callback function.
|
||||
*
|
||||
* Function initializes optional callback function, which should immediately start processing of CO_TIME_process()
|
||||
* function. Callback is called after TIME message is received from the CAN bus.
|
||||
*
|
||||
* @param TIME This object.
|
||||
* @param object Pointer to object, which will be passed to pFunctSignalPre().
|
||||
* @param pFunctSignalPre Pointer to the callback function. Not called if NULL.
|
||||
*/
|
||||
void CO_TIME_initCallbackPre(CO_TIME_t* TIME, void* object, void (*pFunctSignalPre)(void* object));
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Set current time
|
||||
*
|
||||
* @param TIME This object.
|
||||
* @param ms Milliseconds after midnight
|
||||
* @param days Number of days since January 1, 1984
|
||||
* @param producerInterval_ms Interval time for time producer in milliseconds
|
||||
*/
|
||||
static inline void
|
||||
CO_TIME_set(CO_TIME_t* TIME, uint32_t ms, uint16_t days, uint32_t producerInterval_ms) {
|
||||
(void)producerInterval_ms; /* may be unused */
|
||||
|
||||
if (TIME != NULL) {
|
||||
TIME->residual_us = 0;
|
||||
TIME->ms = ms;
|
||||
TIME->days = days;
|
||||
#if ((CO_CONFIG_TIME)&CO_CONFIG_TIME_PRODUCER) != 0
|
||||
TIME->producerTimer_ms = TIME->producerInterval_ms = producerInterval_ms;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process TIME object.
|
||||
*
|
||||
* Function must be called cyclically. It updates internal time from received time stamp message or from
|
||||
* timeDifference_us. It also sends produces timestamp message, if producer and producerInterval_ms is set.
|
||||
*
|
||||
* @param TIME This object.
|
||||
* @param timeDifference_us Time difference from previous function call in [microseconds].
|
||||
* @param NMTisPreOrOperational True if this node is NMT_PRE_OPERATIONAL or NMT_OPERATIONAL state.
|
||||
*
|
||||
* @return True if new TIME stamp message recently received (consumer).
|
||||
*/
|
||||
bool_t CO_TIME_process(CO_TIME_t* TIME, bool_t NMTisPreOrOperational, uint32_t timeDifference_us);
|
||||
|
||||
/** @} */ /* CO_TIME */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_TIME) & CO_CONFIG_TIME_ENABLE */
|
||||
|
||||
#endif /* CO_TIME_H */
|
||||
798
Middleware/CANopenNode/301/CO_config.h
Normal file
798
Middleware/CANopenNode/301/CO_config.h
Normal file
@@ -0,0 +1,798 @@
|
||||
/**
|
||||
* Configuration macros for CANopenNode.
|
||||
*
|
||||
* @file CO_config.h
|
||||
* @ingroup CO_STACK_CONFIG
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2020 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.
|
||||
*/
|
||||
|
||||
#ifndef CO_CONFIG_FLAGS_H
|
||||
#define CO_CONFIG_FLAGS_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_STACK_CONFIG Stack configuration
|
||||
* Stack configuration and enabling macros.
|
||||
*
|
||||
* @ingroup CO_driver
|
||||
*
|
||||
* Default values for stack configuration macros are set in corresponding
|
||||
* header files. The same default values are also provided in this file, but
|
||||
* only for documentation generator. Default values can be overridden by
|
||||
* CO_driver_target.h file. If specified so, they can further be overridden by
|
||||
* CO_driver_custom.h file.
|
||||
*
|
||||
* Stack configuration macro is specified as bits, where each bit
|
||||
* enables/disables some part of the configurable CANopenNode object. Flags are
|
||||
* used for enabling or checking specific bit. Multiple flags can be ORed
|
||||
* together.
|
||||
*
|
||||
* Some functionalities of CANopenNode objects, enabled by configuration macros,
|
||||
* requires some objects from Object Dictionary to exist. Object Dictionary
|
||||
* configuration must match @ref CO_STACK_CONFIG.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup CO_STACK_CONFIG_COMMON Common definitions
|
||||
* Constants for common definitions.
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* Enable custom callback after CAN receive
|
||||
*
|
||||
* Flag enables optional callback functions, which are part of some CANopenNode
|
||||
* objects. Callbacks can optionally be registered by application, which
|
||||
* configures threads in operating system. Callbacks are called after something
|
||||
* has been preprocessed by higher priority thread and must be further
|
||||
* processed by lower priority thread. For example when CAN message is received
|
||||
* and preprocessed, callback should wake up mainline processing function.
|
||||
* See also @ref CO_process() function.
|
||||
*
|
||||
* If callback functions are used, they must be initialized separately, after
|
||||
* the object initialization.
|
||||
*
|
||||
* This flag is common to multiple configuration macros.
|
||||
*/
|
||||
#define CO_CONFIG_FLAG_CALLBACK_PRE 0x1000
|
||||
|
||||
/**
|
||||
* Enable calculation of timerNext_us variable.
|
||||
*
|
||||
* Calculation of the timerNext_us variable is useful for smooth operation on
|
||||
* operating system. See also @ref CO_process() function.
|
||||
*
|
||||
* This flag is common to multiple configuration macros.
|
||||
*/
|
||||
#define CO_CONFIG_FLAG_TIMERNEXT 0x2000
|
||||
|
||||
/**
|
||||
* Enable dynamic behaviour of Object Dictionary variables
|
||||
*
|
||||
* Some CANopen objects uses Object Dictionary variables as arguments to
|
||||
* initialization functions, which are processed in communication reset section.
|
||||
* If this flag is set, then writing to OD variable will reconfigure
|
||||
* corresponding CANopen object also during CANopen normal operation.
|
||||
*
|
||||
* This flag is common to multiple configuration macros.
|
||||
*/
|
||||
#define CO_CONFIG_FLAG_OD_DYNAMIC 0x4000
|
||||
|
||||
/** This flag may be set globally for mainline objects to
|
||||
* @ref CO_CONFIG_FLAG_CALLBACK_PRE */
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_GLOBAL_FLAG_CALLBACK_PRE (0)
|
||||
#endif
|
||||
|
||||
/** This flag may be set globally for Real-Time objects (SYNC, PDO) to
|
||||
* @ref CO_CONFIG_FLAG_CALLBACK_PRE */
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_GLOBAL_RT_FLAG_CALLBACK_PRE (0)
|
||||
#endif
|
||||
|
||||
/** This flag may be set globally to @ref CO_CONFIG_FLAG_TIMERNEXT */
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_GLOBAL_FLAG_TIMERNEXT (0)
|
||||
#endif
|
||||
|
||||
/** This flag may be set globally to (0) */
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_GLOBAL_FLAG_OD_DYNAMIC CO_CONFIG_FLAG_OD_DYNAMIC
|
||||
#endif
|
||||
/** @} */ /* CO_STACK_CONFIG_COMMON */
|
||||
|
||||
/**
|
||||
* @defgroup CO_STACK_CONFIG_NMT_HB NMT master/slave and HB producer/consumer
|
||||
* Specified in standard CiA 301
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* Configuration of @ref CO_NMT_Heartbeat.
|
||||
*
|
||||
* Possible flags, can be ORed:
|
||||
* - CO_CONFIG_NMT_CALLBACK_CHANGE - Enable custom callback after NMT
|
||||
* state changes. Callback is configured by
|
||||
* CO_NMT_initCallbackChanged().
|
||||
* - CO_CONFIG_NMT_MASTER - Enable simple NMT master
|
||||
* - #CO_CONFIG_FLAG_CALLBACK_PRE - Enable custom callback after preprocessing
|
||||
* received NMT CAN message.
|
||||
* Callback is configured by CO_NMT_initCallbackPre().
|
||||
* - #CO_CONFIG_FLAG_TIMERNEXT - Enable calculation of timerNext_us variable
|
||||
* inside CO_NMT_process().
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_NMT (CO_CONFIG_GLOBAL_FLAG_CALLBACK_PRE | CO_CONFIG_GLOBAL_FLAG_TIMERNEXT)
|
||||
#endif
|
||||
#define CO_CONFIG_NMT_CALLBACK_CHANGE 0x01
|
||||
#define CO_CONFIG_NMT_MASTER 0x02
|
||||
|
||||
/**
|
||||
* Configuration of @ref CO_HBconsumer
|
||||
*
|
||||
* Possible flags, can be ORed:
|
||||
* - CO_CONFIG_HB_CONS_ENABLE - Enable heartbeat consumer.
|
||||
* - CO_CONFIG_HB_CONS_CALLBACK_CHANGE - Enable custom common callback after NMT
|
||||
* state of the monitored node changes. Callback is configured by
|
||||
* CO_HBconsumer_initCallbackNmtChanged().
|
||||
* - CO_CONFIG_HB_CONS_CALLBACK_MULTI - Enable multiple custom callbacks, which
|
||||
* can be configured individually for each monitored node. Callbacks are
|
||||
* configured by CO_HBconsumer_initCallbackNmtChanged(),
|
||||
* CO_HBconsumer_initCallbackHeartbeatStarted(),
|
||||
* CO_HBconsumer_initCallbackTimeout() and
|
||||
* CO_HBconsumer_initCallbackRemoteReset() functions.
|
||||
* - CO_CONFIG_HB_CONS_QUERY_FUNCT - Enable functions for query HB state or
|
||||
* NMT state of the specific monitored node.
|
||||
* - #CO_CONFIG_FLAG_CALLBACK_PRE - Enable custom callback after preprocessing
|
||||
* received heartbeat CAN message.
|
||||
* Callback is configured by CO_HBconsumer_initCallbackPre().
|
||||
* - #CO_CONFIG_FLAG_TIMERNEXT - Enable calculation of timerNext_us variable
|
||||
* inside CO_HBconsumer_process().
|
||||
* - #CO_CONFIG_FLAG_OD_DYNAMIC - Enable dynamic configuration of monitored
|
||||
* nodes (Writing to object 0x1016 re-configures the monitored nodes).
|
||||
*
|
||||
* @warning CO_CONFIG_HB_CONS_CALLBACK_CHANGE and
|
||||
* CO_CONFIG_HB_CONS_CALLBACK_MULTI cannot be set simultaneously.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_HB_CONS \
|
||||
(CO_CONFIG_HB_CONS_ENABLE | CO_CONFIG_GLOBAL_FLAG_CALLBACK_PRE | CO_CONFIG_GLOBAL_FLAG_TIMERNEXT \
|
||||
| CO_CONFIG_GLOBAL_FLAG_OD_DYNAMIC)
|
||||
#endif
|
||||
#define CO_CONFIG_HB_CONS_ENABLE 0x01
|
||||
#define CO_CONFIG_HB_CONS_CALLBACK_CHANGE 0x02
|
||||
#define CO_CONFIG_HB_CONS_CALLBACK_MULTI 0x04
|
||||
#define CO_CONFIG_HB_CONS_QUERY_FUNCT 0x08
|
||||
/** @} */ /* CO_STACK_CONFIG_NMT_HB */
|
||||
|
||||
/**
|
||||
* @defgroup CO_STACK_CONFIG_NODE_GUARDING CANopen Node Guarding slave and master objects.
|
||||
* Specified in standard CiA 301
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* Configuration of @ref CO_Node_Guarding
|
||||
*
|
||||
* Possible flags, can be ORed:
|
||||
* - CO_CONFIG_NODE_GUARDING_SLAVE_ENABLE - Enable Node guarding slave.
|
||||
* - CO_CONFIG_NODE_GUARDING_MASTER_ENABLE - Enable Node guarding master.
|
||||
* - #CO_CONFIG_FLAG_TIMERNEXT - Enable calculation of timerNext_us variable
|
||||
* inside CO_nodeGuardingSlave_process().
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_NODE_GUARDING (0)
|
||||
#endif
|
||||
#define CO_CONFIG_NODE_GUARDING_SLAVE_ENABLE 0x01
|
||||
#define CO_CONFIG_NODE_GUARDING_MASTER_ENABLE 0x02
|
||||
|
||||
/**
|
||||
* Maximum number of nodes monitored by master
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_NODE_GUARDING_MASTER_COUNT 0x7F
|
||||
#endif
|
||||
/** @} */ /* CO_STACK_CONFIG_NODE_GUARDING */
|
||||
|
||||
/**
|
||||
* @defgroup CO_STACK_CONFIG_EMERGENCY Emergency producer/consumer
|
||||
* Specified in standard CiA 301
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* Configuration of @ref CO_Emergency
|
||||
*
|
||||
* Possible flags, can be ORed:
|
||||
* - CO_CONFIG_EM_PRODUCER - Enable emergency producer.
|
||||
* - CO_CONFIG_EM_PROD_CONFIGURABLE - Emergency producer COB-ID is configurable,
|
||||
* OD object 0x1014. If not configurable, then 0x1014 is read-only, COB_ID
|
||||
* is set to CO_CAN_ID_EMERGENCY + nodeId and write is not verified.
|
||||
* - CO_CONFIG_EM_PROD_INHIBIT - Enable inhibit timer on emergency producer,
|
||||
* OD object 0x1015.
|
||||
* - CO_CONFIG_EM_HISTORY - Enable error history, OD object 0x1003,
|
||||
* "Pre-defined error field"
|
||||
* - CO_CONFIG_EM_CONSUMER - Enable simple emergency consumer with callback.
|
||||
* - CO_CONFIG_EM_STATUS_BITS - Access @ref CO_EM_errorStatusBits_t from OD.
|
||||
* - #CO_CONFIG_FLAG_CALLBACK_PRE - Enable custom callback after preprocessing
|
||||
* emergency condition by CO_errorReport() or CO_errorReset() call.
|
||||
* Callback is configured by CO_EM_initCallbackPre().
|
||||
* - #CO_CONFIG_FLAG_TIMERNEXT - Enable calculation of timerNext_us variable
|
||||
* inside CO_EM_process().
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_EM \
|
||||
(CO_CONFIG_EM_PRODUCER | CO_CONFIG_EM_HISTORY | CO_CONFIG_GLOBAL_FLAG_CALLBACK_PRE \
|
||||
| CO_CONFIG_GLOBAL_FLAG_TIMERNEXT)
|
||||
#endif
|
||||
#define CO_CONFIG_EM_PRODUCER 0x01
|
||||
#define CO_CONFIG_EM_PROD_CONFIGURABLE 0x02
|
||||
#define CO_CONFIG_EM_PROD_INHIBIT 0x04
|
||||
#define CO_CONFIG_EM_HISTORY 0x08
|
||||
#define CO_CONFIG_EM_STATUS_BITS 0x10
|
||||
#define CO_CONFIG_EM_CONSUMER 0x20
|
||||
|
||||
/**
|
||||
* Maximum number of @ref CO_EM_errorStatusBits_t
|
||||
*
|
||||
* Stack uses 6*8 = 48 @ref CO_EM_errorStatusBits_t, others are free to use by
|
||||
* manufacturer. Allowable value range is from 48 to 256 bits in steps of 8.
|
||||
* Default is 80.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_EM_ERR_STATUS_BITS_COUNT (10 * 8)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Condition for calculating CANopen Error register, "generic" error bit.
|
||||
*
|
||||
* Condition must observe suitable @ref CO_EM_errorStatusBits_t and use
|
||||
* corresponding member of errorStatusBits array from CO_EM_t to calculate the
|
||||
* condition. See also @ref CO_errorRegister_t.
|
||||
*
|
||||
* @warning Size of @ref CO_CONFIG_EM_ERR_STATUS_BITS_COUNT must be large
|
||||
* enough. (CO_CONFIG_EM_ERR_STATUS_BITS_COUNT/8) must be larger than index of
|
||||
* array member in em->errorStatusBits[index].
|
||||
*
|
||||
* em->errorStatusBits[5] should be included in the condition, because they are
|
||||
* used by the stack.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_ERR_CONDITION_GENERIC (em->errorStatusBits[5] != 0)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Condition for calculating CANopen Error register, "current" error bit.
|
||||
* See @ref CO_CONFIG_ERR_CONDITION_GENERIC for description.
|
||||
* Macro is not defined by default, so no error is verified.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_ERR_CONDITION_CURRENT
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Condition for calculating CANopen Error register, "voltage" error bit.
|
||||
* See @ref CO_CONFIG_ERR_CONDITION_GENERIC for description.
|
||||
* Macro is not defined by default, so no error is verified.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_ERR_CONDITION_VOLTAGE
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Condition for calculating CANopen Error register, "temperature" error bit.
|
||||
* See @ref CO_CONFIG_ERR_CONDITION_GENERIC for description.
|
||||
* Macro is not defined by default, so no error is verified.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_ERR_CONDITION_TEMPERATURE
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Condition for calculating CANopen Error register, "communication" error bit.
|
||||
* See @ref CO_CONFIG_ERR_CONDITION_GENERIC for description.
|
||||
*
|
||||
* em->errorStatusBits[2] and em->errorStatusBits[3] must be included in the
|
||||
* condition, because they are used by the stack.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_ERR_CONDITION_COMMUNICATION (em->errorStatusBits[2] || em->errorStatusBits[3])
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Condition for calculating CANopen Error register, "device profile" error bit.
|
||||
* See @ref CO_CONFIG_ERR_CONDITION_GENERIC for description.
|
||||
* Macro is not defined by default, so no error is verified.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_ERR_CONDITION_DEV_PROFILE
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Condition for calculating CANopen Error register, "manufacturer" error bit.
|
||||
* See @ref CO_CONFIG_ERR_CONDITION_GENERIC for description.
|
||||
*
|
||||
* em->errorStatusBits[8] and em->errorStatusBits[8] are pre-defined, but can
|
||||
* be changed.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_ERR_CONDITION_MANUFACTURER (em->errorStatusBits[8] || em->errorStatusBits[9])
|
||||
#endif
|
||||
/** @} */ /* CO_STACK_CONFIG_EMERGENCY */
|
||||
|
||||
/**
|
||||
* @defgroup CO_STACK_CONFIG_SDO SDO server/client
|
||||
* Specified in standard CiA 301
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* Configuration of @ref CO_SDOserver
|
||||
*
|
||||
* Possible flags, can be ORed:
|
||||
* - CO_CONFIG_SDO_SRV_SEGMENTED - Enable SDO server segmented transfer.
|
||||
* - CO_CONFIG_SDO_SRV_BLOCK - Enable SDO server block transfer. If set, then
|
||||
* CO_CONFIG_SDO_SRV_SEGMENTED must also be set.
|
||||
* - #CO_CONFIG_FLAG_CALLBACK_PRE - Enable custom callback after preprocessing
|
||||
* received SDO CAN message.
|
||||
* Callback is configured by CO_SDOserver_initCallbackPre().
|
||||
* - #CO_CONFIG_FLAG_TIMERNEXT - Enable calculation of timerNext_us variable
|
||||
* inside CO_SDOserver_process().
|
||||
* - #CO_CONFIG_FLAG_OD_DYNAMIC - Enable dynamic configuration of additional SDO
|
||||
* servers (Writing to object 0x1201+ re-configures the additional server).
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_SDO_SRV \
|
||||
(CO_CONFIG_SDO_SRV_SEGMENTED | CO_CONFIG_GLOBAL_FLAG_CALLBACK_PRE | CO_CONFIG_GLOBAL_FLAG_TIMERNEXT \
|
||||
| CO_CONFIG_GLOBAL_FLAG_OD_DYNAMIC)
|
||||
#endif
|
||||
#define CO_CONFIG_SDO_SRV_SEGMENTED 0x02
|
||||
#define CO_CONFIG_SDO_SRV_BLOCK 0x04
|
||||
|
||||
/**
|
||||
* Size of the internal data buffer for the SDO server.
|
||||
*
|
||||
* If size is less than size of some variables in Object Dictionary, then data
|
||||
* will be transferred to internal buffer in several segments. Minimum size is
|
||||
* 8 or 899 (127*7) for block transfer.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_SDO_SRV_BUFFER_SIZE 32
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Configuration of @ref CO_SDOclient
|
||||
*
|
||||
* Possible flags, can be ORed:
|
||||
* - CO_CONFIG_SDO_CLI_ENABLE - Enable SDO client.
|
||||
* - CO_CONFIG_SDO_CLI_SEGMENTED - Enable SDO client segmented transfer.
|
||||
* - CO_CONFIG_SDO_CLI_BLOCK - Enable SDO client block transfer. If set, then
|
||||
* CO_CONFIG_SDO_CLI_SEGMENTED, CO_CONFIG_FIFO_ALT_READ and
|
||||
* CO_CONFIG_FIFO_CRC16_CCITT must also be set.
|
||||
* - CO_CONFIG_SDO_CLI_LOCAL - Enable local transfer, if Node-ID of the SDO
|
||||
* server is the same as node-ID of the SDO client. (SDO client is the same
|
||||
* device as SDO server.) Transfer data directly without communication on CAN.
|
||||
* - #CO_CONFIG_FLAG_CALLBACK_PRE - Enable custom callback after preprocessing
|
||||
* received SDO CAN message.
|
||||
* Callback is configured by CO_SDOclient_initCallbackPre().
|
||||
* - #CO_CONFIG_FLAG_TIMERNEXT - Enable calculation of timerNext_us variable
|
||||
* inside CO_SDOclientDownloadInitiate(), CO_SDOclientDownload(),
|
||||
* CO_SDOclientUploadInitiate(), CO_SDOclientUpload().
|
||||
* - #CO_CONFIG_FLAG_OD_DYNAMIC - Enable dynamic configuration of SDO clients
|
||||
* (Writing to object 0x1280+ re-configures the client).
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_SDO_CLI (0)
|
||||
#endif
|
||||
#define CO_CONFIG_SDO_CLI_ENABLE 0x01
|
||||
#define CO_CONFIG_SDO_CLI_SEGMENTED 0x02
|
||||
#define CO_CONFIG_SDO_CLI_BLOCK 0x04
|
||||
#define CO_CONFIG_SDO_CLI_LOCAL 0x08
|
||||
|
||||
/**
|
||||
* Size of the internal data buffer for the SDO client.
|
||||
*
|
||||
* Circular buffer is used for SDO communication. It can be read or written
|
||||
* between successive SDO calls. So size of the buffer can be lower than size of
|
||||
* the actual size of data transferred. If only segmented transfer is used, then
|
||||
* buffer size can be as low as 7 bytes, if data are read/written each cycle. If
|
||||
* block transfer is used, buffer size should be set to at least 1000 bytes, so
|
||||
* maximum blksize can be used (blksize is calculated from free buffer space).
|
||||
* Default value for block transfer is 1000, otherwise 32.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_SDO_CLI_BUFFER_SIZE 32
|
||||
#endif
|
||||
/** @} */ /* CO_STACK_CONFIG_SDO */
|
||||
|
||||
/**
|
||||
* @defgroup CO_STACK_CONFIG_TIME Time producer/consumer
|
||||
* Specified in standard CiA 301
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* Configuration of @ref CO_TIME
|
||||
*
|
||||
* Possible flags, can be ORed:
|
||||
* - CO_CONFIG_TIME_ENABLE - Enable TIME object and TIME consumer.
|
||||
* - CO_CONFIG_TIME_PRODUCER - Enable TIME producer.
|
||||
* - #CO_CONFIG_FLAG_CALLBACK_PRE - Enable custom callback after preprocessing
|
||||
* received TIME CAN message.
|
||||
* Callback is configured by CO_TIME_initCallbackPre().
|
||||
* - #CO_CONFIG_FLAG_OD_DYNAMIC - Enable dynamic configuration - writing to
|
||||
* object 0x1012 enables / disables time producer or consumer.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_TIME (CO_CONFIG_TIME_ENABLE | CO_CONFIG_GLOBAL_FLAG_CALLBACK_PRE | CO_CONFIG_GLOBAL_FLAG_OD_DYNAMIC)
|
||||
#endif
|
||||
#define CO_CONFIG_TIME_ENABLE 0x01
|
||||
#define CO_CONFIG_TIME_PRODUCER 0x02
|
||||
/** @} */ /* CO_STACK_CONFIG_TIME */
|
||||
|
||||
/**
|
||||
* @defgroup CO_STACK_CONFIG_SYNC_PDO SYNC and PDO producer/consumer
|
||||
* Specified in standard CiA 301
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* Configuration of @ref CO_SYNC
|
||||
*
|
||||
* Possible flags, can be ORed:
|
||||
* - CO_CONFIG_SYNC_ENABLE - Enable SYNC object and SYNC consumer.
|
||||
* - CO_CONFIG_SYNC_PRODUCER - Enable SYNC producer.
|
||||
* - #CO_CONFIG_FLAG_CALLBACK_PRE - Enable custom callback after preprocessing
|
||||
* received SYNC CAN message.
|
||||
* Callback is configured by CO_SYNC_initCallbackPre().
|
||||
* - #CO_CONFIG_FLAG_TIMERNEXT - Enable calculation of timerNext_us variable
|
||||
* inside CO_SYNC_process().
|
||||
* - #CO_CONFIG_FLAG_OD_DYNAMIC - Enable dynamic configuration of SYNC.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_SYNC \
|
||||
(CO_CONFIG_SYNC_ENABLE | CO_CONFIG_SYNC_PRODUCER | CO_CONFIG_GLOBAL_RT_FLAG_CALLBACK_PRE \
|
||||
| CO_CONFIG_GLOBAL_FLAG_TIMERNEXT | CO_CONFIG_GLOBAL_FLAG_OD_DYNAMIC)
|
||||
#endif
|
||||
#define CO_CONFIG_SYNC_ENABLE 0x01
|
||||
#define CO_CONFIG_SYNC_PRODUCER 0x02
|
||||
|
||||
/**
|
||||
* Configuration of @ref CO_PDO
|
||||
*
|
||||
* Possible flags, can be ORed:
|
||||
* - CO_CONFIG_RPDO_ENABLE - Enable receive PDO objects.
|
||||
* - CO_CONFIG_TPDO_ENABLE - Enable transmit PDO objects.
|
||||
* - CO_CONFIG_RPDO_TIMERS_ENABLE - Enable RPDO timers: RPDO timeout monitoring
|
||||
* with event timer.
|
||||
* - CO_CONFIG_TPDO_TIMERS_ENABLE - Enable TPDO timers: TPDO inhibit and event
|
||||
* timers.
|
||||
* - CO_CONFIG_PDO_SYNC_ENABLE - Enable SYNC in PDO objects.
|
||||
* - CO_CONFIG_PDO_OD_IO_ACCESS - For OD variables mapped to PDO use read/write
|
||||
* function access with @ref OD_IO_t. This option enables much more
|
||||
* flexibility for application program, but consumes some additional memory
|
||||
* and processor resources. If this option is not enabled, then data from OD
|
||||
* variables are fetched directly from memory allocated by Object dictionary.
|
||||
* - CO_CONFIG_PDO_BITWISE_MAPPING - Use bitwise mapping instead of byte-wise
|
||||
* By default, the OD_IO structure contains the number of bytes mapped to
|
||||
* the PDO in the OD_IO.dataOffset field. If the bitwise mapping is enabled,
|
||||
* this field stores the number of bits mapped to the PDO. Bitwise PDO mapping
|
||||
* is not possible without CO_CONFIG_PDO_OD_IO_ACCESS
|
||||
* - #CO_CONFIG_FLAG_CALLBACK_PRE - Enable custom callback after preprocessing
|
||||
* received RPDO CAN message.
|
||||
* Callback is configured by CO_RPDO_initCallbackPre().
|
||||
* - #CO_CONFIG_FLAG_TIMERNEXT - Enable calculation of timerNext_us variable
|
||||
* inside CO_TPDO_process().
|
||||
* - #CO_CONFIG_FLAG_OD_DYNAMIC - Enable dynamic configuration of PDO.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_PDO \
|
||||
(CO_CONFIG_RPDO_ENABLE | CO_CONFIG_TPDO_ENABLE | CO_CONFIG_RPDO_TIMERS_ENABLE | CO_CONFIG_TPDO_TIMERS_ENABLE \
|
||||
| CO_CONFIG_PDO_SYNC_ENABLE | CO_CONFIG_PDO_OD_IO_ACCESS | CO_CONFIG_GLOBAL_RT_FLAG_CALLBACK_PRE \
|
||||
| CO_CONFIG_GLOBAL_FLAG_TIMERNEXT | CO_CONFIG_GLOBAL_FLAG_OD_DYNAMIC)
|
||||
#endif
|
||||
#define CO_CONFIG_RPDO_ENABLE 0x01
|
||||
#define CO_CONFIG_TPDO_ENABLE 0x02
|
||||
#define CO_CONFIG_RPDO_TIMERS_ENABLE 0x04
|
||||
#define CO_CONFIG_TPDO_TIMERS_ENABLE 0x08
|
||||
#define CO_CONFIG_PDO_SYNC_ENABLE 0x10
|
||||
#define CO_CONFIG_PDO_OD_IO_ACCESS 0x20
|
||||
#define CO_CONFIG_PDO_BITWISE_MAPPING 0x40
|
||||
/** @} */ /* CO_STACK_CONFIG_SYNC_PDO */
|
||||
|
||||
/**
|
||||
* @defgroup CO_STACK_CONFIG_STORAGE Data storage
|
||||
* Data storage with CANopen OD objects 1010 and 1011, CiA 301
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* Configuration of @ref CO_storage
|
||||
*
|
||||
* Possible flags, can be ORed:
|
||||
* - CO_CONFIG_STORAGE_ENABLE - Enable data storage
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_STORAGE (CO_CONFIG_STORAGE_ENABLE)
|
||||
#endif
|
||||
#define CO_CONFIG_STORAGE_ENABLE 0x01
|
||||
/** @} */ /* CO_STACK_CONFIG_STORAGE */
|
||||
|
||||
/**
|
||||
* @defgroup CO_STACK_CONFIG_LEDS CANopen LED diodes
|
||||
* Specified in standard CiA 303-3
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* Configuration of @ref CO_LEDs
|
||||
*
|
||||
* Possible flags, can be ORed:
|
||||
* - CO_CONFIG_LEDS_ENABLE - Enable calculation of the CANopen LED indicators.
|
||||
* - #CO_CONFIG_FLAG_TIMERNEXT - Enable calculation of timerNext_us variable
|
||||
* inside CO_NMT_process().
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_LEDS (CO_CONFIG_LEDS_ENABLE | CO_CONFIG_GLOBAL_FLAG_TIMERNEXT)
|
||||
#endif
|
||||
#define CO_CONFIG_LEDS_ENABLE 0x01
|
||||
/** @} */ /* CO_STACK_CONFIG_LEDS */
|
||||
|
||||
/**
|
||||
* @defgroup CO_STACK_CONFIG_SRDO Safety Related Data Objects (SRDO)
|
||||
* Specified in standard EN 50325-5 (CiA 304)
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* Configuration of @ref CO_GFC
|
||||
*
|
||||
* Possible flags, can be ORed:
|
||||
* - CO_CONFIG_GFC_ENABLE - Enable the GFC object
|
||||
* - CO_CONFIG_GFC_CONSUMER - Enable the GFC consumer
|
||||
* - CO_CONFIG_GFC_PRODUCER - Enable the GFC producer
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_GFC (0)
|
||||
#endif
|
||||
#define CO_CONFIG_GFC_ENABLE 0x01
|
||||
#define CO_CONFIG_GFC_CONSUMER 0x02
|
||||
#define CO_CONFIG_GFC_PRODUCER 0x04
|
||||
|
||||
/**
|
||||
* Configuration of @ref CO_SRDO
|
||||
*
|
||||
* Possible flags, can be ORed:
|
||||
* - CO_CONFIG_SRDO_ENABLE - Enable the SRDO object.
|
||||
* - CO_CONFIG_SRDO_CHECK_TX - Enable checking data before sending.
|
||||
* - #CO_CONFIG_FLAG_CALLBACK_PRE - Enable custom callback after preprocessing
|
||||
* received RSRDO CAN message.
|
||||
* Callback is configured by CO_SRDO_initCallbackPre().
|
||||
* - #CO_CONFIG_FLAG_TIMERNEXT - Enable calculation of timerNext_us variable
|
||||
* inside CO_SRDO_process() (Tx SRDO only).
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_SRDO (0)
|
||||
#endif
|
||||
#define CO_CONFIG_SRDO_ENABLE 0x01
|
||||
#define CO_CONFIG_SRDO_CHECK_TX 0x02
|
||||
|
||||
/**
|
||||
* SRDO Tx time delay
|
||||
*
|
||||
* minimum time between the first and second SRDO (Tx) message
|
||||
* in us
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_SRDO_MINIMUM_DELAY 0
|
||||
#endif
|
||||
/** @} */ /* CO_STACK_CONFIG_SRDO */
|
||||
|
||||
/**
|
||||
* @defgroup CO_STACK_CONFIG_LSS LSS master/slave
|
||||
* Specified in standard CiA 305
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* Configuration of @ref CO_LSS
|
||||
*
|
||||
* Possible flags, can be ORed:
|
||||
* - CO_CONFIG_LSS_SLAVE - Enable LSS slave
|
||||
* - CO_CONFIG_LSS_SLAVE_FASTSCAN_DIRECT_RESPOND - Send LSS fastscan respond
|
||||
* directly from CO_LSSslave_receive() function.
|
||||
* - CO_CONFIG_LSS_MASTER - Enable LSS master
|
||||
* - #CO_CONFIG_FLAG_CALLBACK_PRE - Enable custom callback after preprocessing
|
||||
* received CAN message.
|
||||
* Callback is configured by CO_LSSmaster_initCallbackPre().
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_LSS (CO_CONFIG_LSS_SLAVE | CO_CONFIG_GLOBAL_FLAG_CALLBACK_PRE)
|
||||
#endif
|
||||
#define CO_CONFIG_LSS_SLAVE 0x01
|
||||
#define CO_CONFIG_LSS_SLAVE_FASTSCAN_DIRECT_RESPOND 0x02
|
||||
#define CO_CONFIG_LSS_MASTER 0x10
|
||||
/** @} */ /* CO_STACK_CONFIG_LSS */
|
||||
|
||||
/**
|
||||
* @defgroup CO_STACK_CONFIG_GATEWAY CANopen gateway
|
||||
* Specified in standard CiA 309
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* Configuration of @ref CO_CANopen_309_3
|
||||
*
|
||||
* Gateway object is covered by standard CiA 309 - CANopen access from other
|
||||
* networks. It enables usage of the NMT master, SDO client and LSS master as a
|
||||
* gateway device.
|
||||
*
|
||||
* Possible flags, can be ORed:
|
||||
* - CO_CONFIG_GTW_MULTI_NET - Enable multiple network interfaces in gateway
|
||||
* device. This functionality is currently not implemented.
|
||||
* - CO_CONFIG_GTW_ASCII - Enable gateway device with ASCII mapping (CiA 309-3)
|
||||
* If set, then CO_CONFIG_FIFO_ASCII_COMMANDS must also be set.
|
||||
* - CO_CONFIG_GTW_ASCII_SDO - Enable SDO client. If set, then
|
||||
* CO_CONFIG_FIFO_ASCII_DATATYPES must also be set.
|
||||
* - CO_CONFIG_GTW_ASCII_NMT - Enable NMT master
|
||||
* - CO_CONFIG_GTW_ASCII_LSS - Enable LSS master
|
||||
* - CO_CONFIG_GTW_ASCII_LOG - Enable non-standard message log read
|
||||
* - CO_CONFIG_GTW_ASCII_ERROR_DESC - Print error description as additional
|
||||
* comments in gateway-ascii device for SDO and gateway errors.
|
||||
* - CO_CONFIG_GTW_ASCII_PRINT_HELP - use non-standard command "help" to print
|
||||
* help usage.
|
||||
* - CO_CONFIG_GTW_ASCII_PRINT_LEDS - Display "red" and "green" CANopen status
|
||||
* LED diodes on terminal.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_GTW (0)
|
||||
#endif
|
||||
#define CO_CONFIG_GTW_MULTI_NET 0x01
|
||||
#define CO_CONFIG_GTW_ASCII 0x02
|
||||
#define CO_CONFIG_GTW_ASCII_SDO 0x04
|
||||
#define CO_CONFIG_GTW_ASCII_NMT 0x08
|
||||
#define CO_CONFIG_GTW_ASCII_LSS 0x10
|
||||
#define CO_CONFIG_GTW_ASCII_LOG 0x20
|
||||
#define CO_CONFIG_GTW_ASCII_ERROR_DESC 0x40
|
||||
#define CO_CONFIG_GTW_ASCII_PRINT_HELP 0x80
|
||||
#define CO_CONFIG_GTW_ASCII_PRINT_LEDS 0x100
|
||||
|
||||
/**
|
||||
* Number of loops of #CO_SDOclientDownload() in case of block download
|
||||
*
|
||||
* If SDO clint has block download in progress and OS has buffer for CAN tx
|
||||
* messages, then #CO_SDOclientDownload() functionion can be called multiple
|
||||
* times within own loop (up to 127). This can speed-up SDO block transfer.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_GTW_BLOCK_DL_LOOP 1
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Size of command buffer in ASCII gateway object.
|
||||
*
|
||||
* If large amount of data is transferred (block transfer), then this should be
|
||||
* increased to 1000 or more. Buffer may be refilled between the block transfer.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_GTWA_COMM_BUF_SIZE 200
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Size of message log buffer in ASCII gateway object.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_GTWA_LOG_BUF_SIZE 2000
|
||||
#endif
|
||||
/** @} */ /* CO_STACK_CONFIG_GATEWAY */
|
||||
|
||||
/**
|
||||
* @defgroup CO_STACK_CONFIG_CRC16 CRC 16 calculation
|
||||
* Helper object for CRC-16 checksum
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* Configuration of @ref CO_crc16_ccitt calculation
|
||||
*
|
||||
* Possible flags, can be ORed:
|
||||
* - CO_CONFIG_CRC16_ENABLE - Enable CRC16 calculation
|
||||
* - CO_CONFIG_CRC16_EXTERNAL - CRC functions are defined externally
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_CRC16 (0)
|
||||
#endif
|
||||
#define CO_CONFIG_CRC16_ENABLE 0x01
|
||||
#define CO_CONFIG_CRC16_EXTERNAL 0x02
|
||||
/** @} */ /* CO_STACK_CONFIG_CRC16 */
|
||||
|
||||
/**
|
||||
* @defgroup CO_STACK_CONFIG_FIFO FIFO buffer
|
||||
* Helper object for FIFO buffer
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* Configuration of @ref CO_CANopen_301_fifo
|
||||
*
|
||||
* FIFO buffer is basically a simple first-in first-out circular data buffer. It
|
||||
* is used by the SDO client and by the CANopen gateway. It has additional
|
||||
* advanced functions for data passed to FIFO.
|
||||
*
|
||||
*
|
||||
* Possible flags, can be ORed:
|
||||
* - CO_CONFIG_FIFO_ENABLE - Enable FIFO buffer
|
||||
* - CO_CONFIG_FIFO_ALT_READ - This must be enabled, when SDO client has
|
||||
* CO_CONFIG_SDO_CLI_BLOCK enabled. See @ref CO_fifo_altRead().
|
||||
* - CO_CONFIG_FIFO_CRC16_CCITT - This must be enabled, when SDO client has
|
||||
* CO_CONFIG_SDO_CLI_BLOCK enabled. It enables CRC calculation on data.
|
||||
* - CO_CONFIG_FIFO_ASCII_COMMANDS - This must be enabled, when CANopen gateway
|
||||
* has CO_CONFIG_GTW_ASCII enabled. It adds command handling functions.
|
||||
* - CO_CONFIG_FIFO_ASCII_DATATYPES - This must be enabled, when CANopen gateway
|
||||
* has CO_CONFIG_GTW_ASCII and CO_CONFIG_GTW_ASCII_SDO enabled. It adds
|
||||
* datatype transform functions between binary and ascii, which are necessary
|
||||
* for SDO client.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_FIFO (0)
|
||||
#endif
|
||||
#define CO_CONFIG_FIFO_ENABLE 0x01
|
||||
#define CO_CONFIG_FIFO_ALT_READ 0x02
|
||||
#define CO_CONFIG_FIFO_CRC16_CCITT 0x04
|
||||
#define CO_CONFIG_FIFO_ASCII_COMMANDS 0x08
|
||||
#define CO_CONFIG_FIFO_ASCII_DATATYPES 0x10
|
||||
/** @} */ /* CO_STACK_CONFIG_FIFO */
|
||||
|
||||
/**
|
||||
* @defgroup CO_STACK_CONFIG_TRACE Trace recorder
|
||||
* Non standard object
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* Configuration of @ref CO_trace for recording variables over time.
|
||||
*
|
||||
* Possible flags, can be ORed:
|
||||
* - CO_CONFIG_TRACE_ENABLE - Enable Trace recorder
|
||||
* - CO_CONFIG_TRACE_OWN_INTTYPES - If set, then macros PRIu32("u" or "lu")
|
||||
* and PRId32("d" or "ld") must be set. (File inttypes.h can not be included).
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_TRACE (0)
|
||||
#endif
|
||||
#define CO_CONFIG_TRACE_ENABLE 0x01
|
||||
#define CO_CONFIG_TRACE_OWN_INTTYPES 0x02
|
||||
/** @} */ /* CO_STACK_CONFIG_TRACE */
|
||||
|
||||
/**
|
||||
* @defgroup CO_STACK_CONFIG_DEBUG Debug messages
|
||||
* Messages from different parts of the stack.
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* Configuration of debug messages from different parts of the stack, which can
|
||||
* be logged according to target specific function.
|
||||
*
|
||||
* Possible flags, can be ORed:
|
||||
* - CO_CONFIG_DEBUG_COMMON - Define default CO_DEBUG_COMMON(msg) macro. This
|
||||
* macro is target specific. This macro is then used as default macro in all
|
||||
* other defined CO_DEBUG_XXX(msg) macros.
|
||||
* - CO_CONFIG_DEBUG_SDO_CLIENT - Define default CO_DEBUG_SDO_CLIENT(msg) macro.
|
||||
* - CO_CONFIG_DEBUG_SDO_SERVER - Define default CO_DEBUG_SDO_SERVER(msg) macro.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_CONFIG_DEBUG (0)
|
||||
#endif
|
||||
#define CO_CONFIG_DEBUG_COMMON 0x01
|
||||
#define CO_CONFIG_DEBUG_SDO_CLIENT 0x02
|
||||
#define CO_CONFIG_DEBUG_SDO_SERVER 0x04
|
||||
/** @} */ /* CO_STACK_CONFIG_DEBUG */
|
||||
|
||||
/** @} */ /* CO_STACK_CONFIG */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CO_CONFIG_FLAGS_H */
|
||||
657
Middleware/CANopenNode/301/CO_driver.h
Normal file
657
Middleware/CANopenNode/301/CO_driver.h
Normal file
@@ -0,0 +1,657 @@
|
||||
/**
|
||||
* Interface between CAN hardware and CANopenNode.
|
||||
*
|
||||
* @file CO_driver.h
|
||||
* @ingroup CO_driver
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2004 - 2020 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.
|
||||
*/
|
||||
|
||||
#ifndef CO_DRIVER_H
|
||||
#define CO_DRIVER_H
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "CO_config.h"
|
||||
#include "CO_driver_target.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Stack configuration default global values. For more information see file CO_config.h. */
|
||||
#ifndef CO_CONFIG_GLOBAL_FLAG_CALLBACK_PRE
|
||||
#define CO_CONFIG_GLOBAL_FLAG_CALLBACK_PRE (0)
|
||||
#endif
|
||||
#ifndef CO_CONFIG_GLOBAL_RT_FLAG_CALLBACK_PRE
|
||||
#define CO_CONFIG_GLOBAL_RT_FLAG_CALLBACK_PRE (0)
|
||||
#endif
|
||||
#ifndef CO_CONFIG_GLOBAL_FLAG_TIMERNEXT
|
||||
#define CO_CONFIG_GLOBAL_FLAG_TIMERNEXT (0)
|
||||
#endif
|
||||
#ifndef CO_CONFIG_GLOBAL_FLAG_OD_DYNAMIC
|
||||
#define CO_CONFIG_GLOBAL_FLAG_OD_DYNAMIC CO_CONFIG_FLAG_OD_DYNAMIC
|
||||
#endif
|
||||
#ifdef CO_DEBUG_COMMON
|
||||
#if (CO_CONFIG_DEBUG) & CO_CONFIG_DEBUG_SDO_CLIENT
|
||||
#define CO_DEBUG_SDO_CLIENT(msg) CO_DEBUG_COMMON(msg)
|
||||
#endif
|
||||
#if (CO_CONFIG_DEBUG) & CO_CONFIG_DEBUG_SDO_SERVER
|
||||
#define CO_DEBUG_SDO_SERVER(msg) CO_DEBUG_COMMON(msg)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_driver Driver
|
||||
* Interface between CAN hardware and CANopenNode.
|
||||
*
|
||||
* @ingroup CO_CANopen_301
|
||||
* @{
|
||||
* CANopenNode is designed for speed and portability. It runs efficiently on devices from simple 16-bit microcontrollers
|
||||
* to PC computers. It can run in multiple threads. Reception of CAN messages is pre-processed with very fast functions.
|
||||
* Time critical objects, such as PDO or SYNC are processed in real-time thread and other objects are processed in
|
||||
* normal thread. See Flowchart in [README.md](index.html) for more information.
|
||||
*
|
||||
* @anchor CO_obj
|
||||
* #### CANopenNode Object
|
||||
* CANopenNode is implemented as a collection of different objects, for example SDO, SYNC, Emergency, PDO, NMT,
|
||||
* Heartbeat, etc. Code is written in C language and tries to be object oriented. So each CANopenNode Object is
|
||||
* implemented in a pair of .h/.c files. It basically contains a structure with all necessary variables and some
|
||||
* functions which operates on it. CANopenNode Object is usually connected with one or more CAN receive or transmit
|
||||
* Message Objects. (CAN message Object is a CAN message with specific 11-bit CAN identifier (usually one fixed or a
|
||||
* range).)
|
||||
*
|
||||
* #### Hardware interface of CANopenNode
|
||||
* It consists of minimum three files:
|
||||
* - **CO_driver.h** file declares common functions. This file is part of the CANopenNode. It is included from each .c
|
||||
* file from CANopenNode.
|
||||
* - **CO_driver_target.h** file declares microcontroller specific type declarations and defines some macros, which are
|
||||
* necessary for CANopenNode. This file is included from CO_driver.h.
|
||||
* - **CO_driver.c** file defines functions declared in CO_driver.h.
|
||||
*
|
||||
* **CO_driver_target.h** and **CO_driver.c** files are specific for each different microcontroller and are not part of
|
||||
* CANopenNode. There are separate projects for different microcontrollers, which usually include CANopenNode as a git
|
||||
* submodule. CANopenNode only includes those two files in the `example` directory and they are basically empty. It
|
||||
* should be possible to compile the `CANopenNode/example` on any system, however compiled program is not usable.
|
||||
* CO_driver.h contains documentation for all necessary macros, types and functions.
|
||||
*
|
||||
* See [CANopenNode/Wiki](https://github.com/CANopenNode/CANopenNode/wiki) for a known list of available implementations
|
||||
* of CANopenNode on different systems and microcontrollers. Everybody is welcome to extend the list with a link to his
|
||||
* own implementation.
|
||||
*
|
||||
* Implementation of the hardware interface for specific microcontroller is not always an easy task. For reliable and
|
||||
* efficient operation it is necessary to know some parts of the target microcontroller in detail (for example threads
|
||||
* (or interrupts), CAN module, etc.).
|
||||
*/
|
||||
|
||||
/** Major version number of CANopenNode */
|
||||
#define CO_VERSION_MAJOR 4
|
||||
/** Minor version number of CANopenNode */
|
||||
#define CO_VERSION_MINOR 0
|
||||
|
||||
/* Macros and declarations in following part are only used for documentation. */
|
||||
#ifdef CO_DOXYGEN
|
||||
/**
|
||||
* @defgroup CO_dataTypes Basic definitions
|
||||
* @{
|
||||
*
|
||||
* Target specific basic definitions and data types.
|
||||
*
|
||||
* Must be defined in the **CO_driver_target.h** file.
|
||||
*
|
||||
* Depending on processor or compiler architecture, one of the two macros must be defined: CO_LITTLE_ENDIAN or
|
||||
* CO_BIG_ENDIAN. CANopen itself is little endian.
|
||||
*
|
||||
* Basic data types may be specified differently on different architectures. Usually `true` and `false` are defined in
|
||||
* `<stdbool.h>`, `NULL` is defined in `<stddef.h>`, `int8_t` to `uint64_t` are defined in `<stdint.h>`.
|
||||
*/
|
||||
#define CO_LITTLE_ENDIAN /**< CO_LITTLE_ENDIAN or CO_BIG_ENDIAN must be defined */
|
||||
#define CO_SWAP_16(x) x /**< Macro must swap bytes, if CO_BIG_ENDIAN is defined */
|
||||
#define CO_SWAP_32(x) x /**< Macro must swap bytes, if CO_BIG_ENDIAN is defined */
|
||||
#define CO_SWAP_64(x) x /**< Macro must swap bytes, if CO_BIG_ENDIAN is defined */
|
||||
#define NULL (0) /**< NULL, for general usage */
|
||||
#define true 1 /**< Logical true, for general use */
|
||||
#define false 0 /**< Logical false, for general use */
|
||||
typedef uint_fast8_t bool_t; /**< Boolean data type for general use */
|
||||
typedef signed char int8_t; /**< INTEGER8 in CANopen (0002h), 8-bit signed integer */
|
||||
typedef signed int int16_t; /**< INTEGER16 in CANopen (0003h), 16-bit signed integer */
|
||||
typedef signed long int int32_t; /**< INTEGER32 in CANopen (0004h), 32-bit signed integer */
|
||||
typedef signed long long int int64_t; /**< INTEGER64 in CANopen (0015h), 64-bit signed integer */
|
||||
typedef unsigned char uint8_t; /**< UNSIGNED8 in CANopen (0005h), 8-bit unsigned integer */
|
||||
typedef unsigned int uint16_t; /**< UNSIGNED16 in CANopen (0006h), 16-bit unsigned integer */
|
||||
typedef unsigned long int uint32_t; /**< UNSIGNED32 in CANopen (0007h), 32-bit unsigned integer */
|
||||
typedef unsigned long long int uint64_t; /**< UNSIGNED64 in CANopen (001Bh), 64-bit unsigned integer */
|
||||
typedef float float32_t; /**< REAL32 in CANopen (0008h), single precision floating point value, 32-bit */
|
||||
typedef double float64_t; /**< REAL64 in CANopen (0011h), double precision floating point value, 64-bit */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup CO_CAN_Message_reception Reception of CAN messages
|
||||
* @{
|
||||
*
|
||||
* Target specific definitions and description of CAN message reception
|
||||
*
|
||||
* CAN messages in CANopenNode are usually received by its own thread or higher priority interrupt. Received CAN
|
||||
* messages are first filtered by hardware or by software. Thread then examines its 11-bit CAN-id and mask and
|
||||
* determines, to which \ref CO_obj "CANopenNode Object" it belongs to. After that it calls predefined CANrx_callback()
|
||||
* function, which quickly pre-processes the message and fetches the relevant data. CANrx_callback() function is defined
|
||||
* by each \ref CO_obj "CANopenNode Object" separately. Pre-processed fetched data are later processed in another
|
||||
* thread.
|
||||
*
|
||||
* If \ref CO_obj "CANopenNode Object" reception of specific CAN message, it must first configure its own CO_CANrx_t
|
||||
* object with the CO_CANrxBufferInit() function.
|
||||
*/
|
||||
|
||||
/**
|
||||
* CAN receive callback function which pre-processes received CAN message
|
||||
*
|
||||
* It is called by fast CAN receive thread. Each \ref CO_obj "CANopenNode Object" defines its own and registers it with
|
||||
* CO_CANrxBufferInit(), by passing function pointer.
|
||||
*
|
||||
* @param object pointer to specific \ref CO_obj "CANopenNode Object", registered with CO_CANrxBufferInit()
|
||||
* @param rxMsg pointer to received CAN message
|
||||
*/
|
||||
void CANrx_callback(void* object, void* rxMsg);
|
||||
|
||||
/**
|
||||
* CANrx_callback() can read CAN identifier from received CAN message
|
||||
*
|
||||
* Must be defined in the **CO_driver_target.h** file.
|
||||
*
|
||||
* This is target specific function and is specific for specific microcontroller. It is best to implement it by using
|
||||
* inline function or macro. `rxMsg` parameter should cast to a pointer to structure. For best efficiency structure may
|
||||
* have the same alignment as CAN registers inside CAN module.
|
||||
*
|
||||
* @param rxMsg Pointer to received message
|
||||
* @return 11-bit CAN standard identifier.
|
||||
*/
|
||||
static inline uint16_t
|
||||
CO_CANrxMsg_readIdent(void* rxMsg) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* CANrx_callback() can read Data Length Code from received CAN message
|
||||
*
|
||||
* See also CO_CANrxMsg_readIdent():
|
||||
*
|
||||
* @param rxMsg Pointer to received message
|
||||
* @return data length in bytes (0 to 8)
|
||||
*/
|
||||
static inline uint8_t
|
||||
CO_CANrxMsg_readDLC(void* rxMsg) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* CANrx_callback() can read pointer to data from received CAN message
|
||||
*
|
||||
* See also CO_CANrxMsg_readIdent():
|
||||
*
|
||||
* @param rxMsg Pointer to received message
|
||||
* @return pointer to data buffer
|
||||
*/
|
||||
static inline const uint8_t*
|
||||
CO_CANrxMsg_readData(void* rxMsg) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration object for CAN received message for specific \ref CO_obj "CANopenNode Object".
|
||||
*
|
||||
* Must be defined in the **CO_driver_target.h** file.
|
||||
*
|
||||
* Data fields of this structure are used exclusively by the driver. Usually it has the following data fields, but they
|
||||
* may differ for different microcontrollers. Array of multiple CO_CANrx_t objects is included inside CO_CANmodule_t.
|
||||
*/
|
||||
typedef struct {
|
||||
uint16_t ident; /**< Standard CAN Identifier (bits 0..10) + RTR (bit 11) */
|
||||
uint16_t mask; /**< Standard CAN Identifier mask with the same alignment as ident */
|
||||
void* object; /**< \ref CO_obj "CANopenNode Object" initialized in from CO_CANrxBufferInit() */
|
||||
void (*pCANrx_callback)(void* object,
|
||||
void* message); /**< Pointer to CANrx_callback() initialized in CO_CANrxBufferInit() */
|
||||
} CO_CANrx_t;
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup CO_CAN_Message_transmission Transmission of CAN messages
|
||||
* @{
|
||||
*
|
||||
* Target specific definitions and description of CAN message transmission
|
||||
*
|
||||
* If \ref CO_obj "CANopenNode Object" needs transmitting CAN message, it must first configure its own CO_CANtx_t object
|
||||
* with the CO_CANtxBufferInit() function. CAN message can then be sent with CO_CANsend() function. If at that moment
|
||||
* CAN transmit buffer inside microcontroller's CAN module is free, message is copied directly to the CAN module.
|
||||
* Otherwise CO_CANsend() function sets _bufferFull_ flag to true. Message will be then sent by CAN TX interrupt as soon
|
||||
* as CAN module is freed. Until message is not copied to CAN module, its contents must not change. If there are
|
||||
* multiple CO_CANtx_t objects with _bufferFull_ flag set to true, then CO_CANtx_t with lower index will be sent first.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Configuration object for CAN transmit message for specific \ref CO_obj "CANopenNode Object".
|
||||
*
|
||||
* Must be defined in the **CO_driver_target.h** file.
|
||||
*
|
||||
* Data fields of this structure are used exclusively by the driver. Usually it has the following data fields, but they
|
||||
* may differ for different microcontrollers. Array of multiple CO_CANtx_t objects is included inside CO_CANmodule_t.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t ident; /**< CAN identifier as aligned in CAN module */
|
||||
uint8_t DLC; /**< Length of CAN message */
|
||||
uint8_t data[8]; /**< 8 data bytes */
|
||||
volatile bool_t bufferFull; /**< True if previous message is still in the buffer */
|
||||
volatile bool_t syncFlag; /**< Synchronous PDO messages has this flag set. It prevents them to be sent outside the
|
||||
synchronous window */
|
||||
} CO_CANtx_t;
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Complete CAN module object.
|
||||
*
|
||||
* Must be defined in the **CO_driver_target.h** file.
|
||||
*
|
||||
* Usually it has the following data fields, but they may differ for different microcontrollers.
|
||||
*/
|
||||
typedef struct {
|
||||
void* CANptr; /**< From CO_CANmodule_init() */
|
||||
CO_CANrx_t* rxArray; /**< From CO_CANmodule_init() */
|
||||
uint16_t rxSize; /**< From CO_CANmodule_init() */
|
||||
CO_CANtx_t* txArray; /**< From CO_CANmodule_init() */
|
||||
uint16_t txSize; /**< From CO_CANmodule_init() */
|
||||
uint16_t CANerrorStatus; /**< CAN error status bitfield, see @ref CO_CAN_ERR_status_t */
|
||||
volatile bool_t CANnormal; /**< CAN module is in normal mode */
|
||||
volatile bool_t useCANrxFilters; /**< Value different than zero indicates, that CAN module hardware filters are used
|
||||
for CAN reception. If there is not enough hardware filters, they won't be used.
|
||||
In this case will be *all* received CAN messages processed by software. */
|
||||
volatile bool_t bufferInhibitFlag; /**< If flag is true, then message in transmit buffer is synchronous PDO message,
|
||||
which will be aborted, if CO_clearPendingSyncPDOs() function will be called by
|
||||
application. This may be necessary if Synchronous window time was expired. */
|
||||
volatile bool_t
|
||||
firstCANtxMessage; /**< Equal to 1, when the first transmitted message (bootup message) is in CAN TX buffers */
|
||||
volatile uint16_t
|
||||
CANtxCount; /**< Number of messages in transmit buffer, which are waiting to be copied to the CAN module */
|
||||
uint32_t errOld; /**< Previous state of CAN errors */
|
||||
} CO_CANmodule_t;
|
||||
|
||||
/**
|
||||
* Data storage object for one entry.
|
||||
*
|
||||
* Must be defined in the **CO_driver_target.h** file.
|
||||
*
|
||||
* For more information on Data storage see @ref CO_storage or **CO_storage.h** file. Structure members documented here
|
||||
* are always required or required with @ref CO_storage_eeprom. Target system may add own additional, hardware specific
|
||||
* variables.
|
||||
*/
|
||||
typedef struct {
|
||||
void* addr; /**< Address of data to store, always required. */
|
||||
size_t len; /**< Length of data to store, always required. */
|
||||
uint8_t subIndexOD; /**< Sub index in OD objects 1010 and 1011, from 2 to 127. Writing 0x65766173 to 1010,subIndexOD
|
||||
will store data to non-volatile memory Writing 0x64616F6C to 1011,subIndexOD will restore
|
||||
default data, always required. */
|
||||
uint8_t attr; /**< Attribute from @ref CO_storage_attributes_t, always required. */
|
||||
void* storageModule; /**< Pointer to storage module, target system specific usage, required with @ref
|
||||
CO_storage_eeprom. */
|
||||
uint16_t crc; /**< CRC checksum of the data stored in eeprom, set on store, required with @ref CO_storage_eeprom. */
|
||||
size_t eepromAddrSignature; /**< Address of entry signature inside eeprom, set by init, required with @ref
|
||||
CO_storage_eeprom. */
|
||||
size_t eepromAddr; /**< Address of data inside eeprom, set by init, required with @ref CO_storage_eeprom. */
|
||||
size_t offset; /**< Offset of next byte being updated by automatic storage, required with @ref CO_storage_eeprom. */
|
||||
void* additionalParameters; /**< Additional target specific parameters, optional. */
|
||||
} CO_storage_entry_t;
|
||||
|
||||
/**
|
||||
* @defgroup CO_critical_sections Critical sections
|
||||
* @{
|
||||
*
|
||||
* Protection of critical sections in multi-threaded operation.
|
||||
*
|
||||
* CANopenNode is designed to run in different threads, as described in [README.md](index.html). Threads are implemented
|
||||
* differently in different systems. In microcontrollers threads are interrupts with different priorities, for example.
|
||||
* It is necessary to protect sections, where different threads access to the same resource. In simple systems
|
||||
* interrupts or scheduler may be temporary disabled between access to the shared resource. Otherwise mutexes or
|
||||
* semaphores can be used.
|
||||
*
|
||||
* #### Reentrant functions
|
||||
* Functions CO_CANsend() from C_driver.h, and CO_error() from CO_Emergency.h may be called from different threads.
|
||||
* Critical sections must be protected. Either by disabling scheduler or interrupts or by mutexes or semaphores.
|
||||
* Lock/unlock macro is called with pointer to CAN module, which may be used inside.
|
||||
*
|
||||
* #### Object Dictionary variables
|
||||
* In general, there are two threads, which accesses OD variables: mainline (initialization, storage, SDO access) and
|
||||
* timer (PDO access). CANopenNode uses locking mechanism, where SDO server (or other mainline code) prevents execution
|
||||
* of the real-time thread at the moment it reads or writes OD variable. CO_LOCK_OD(CAN_MODULE) and
|
||||
* CO_UNLOCK_OD(CAN_MODULE) macros are used to protect:
|
||||
* - Whole real-time thread,
|
||||
* - SDO server protects read/write access to OD variable. Locking of long OD variables, not accessible from real-time
|
||||
* thread, may block RT thread.
|
||||
* - Any mainline code, which accesses PDO-mappable OD variable, must protect read/write with locking macros. Use @ref
|
||||
* OD_mappable() for check.
|
||||
* - Other cases, where non-PDO-mappable OD variable is used inside real-time thread by some other part of the user
|
||||
* application must be considered with special care. Also when there are multiple threads accessing the OD
|
||||
* (e.g. when using a RTOS), you should always lock the OD.
|
||||
*
|
||||
* #### Synchronization functions for CAN receive
|
||||
* After CAN message is received, it is pre-processed in CANrx_callback(), which copies some data into appropriate
|
||||
* object and at the end sets **new_message** flag. This flag is then pooled in another thread, which further processes
|
||||
* the message. The problem is, that compiler optimization may shuffle memory operations, so it is necessary to ensure,
|
||||
* that **new_message** flag is surely set at the end. It is necessary to use [Memory
|
||||
* barrier](https://en.wikipedia.org/wiki/Memory_barrier).
|
||||
*
|
||||
* If receive function runs inside IRQ, no further synchronization is needed. Otherwise, some kind of synchronization
|
||||
* has to be included. The following example uses GCC builtin memory barrier `__sync_synchronize()`. More information
|
||||
* can be found [here](https://stackoverflow.com/questions/982129/what-does-sync-synchronize-do#982179).
|
||||
*/
|
||||
|
||||
#define CO_LOCK_CAN_SEND(CAN_MODULE) /**< Lock critical section in CO_CANsend() */
|
||||
#define CO_UNLOCK_CAN_SEND(CAN_MODULE) /**< Unlock critical section in CO_CANsend() */
|
||||
#define CO_LOCK_EMCY(CAN_MODULE) /**< Lock critical section in CO_errorReport() or CO_errorReset() */
|
||||
#define CO_UNLOCK_EMCY(CAN_MODULE) /**< Unlock critical section in CO_errorReport() or CO_errorReset() */
|
||||
#define CO_LOCK_OD(CAN_MODULE) /**< Lock critical section when accessing Object Dictionary */
|
||||
#define CO_UNLOCK_OD(CAN_MODULE) /**< Unock critical section when accessing Object Dictionary */
|
||||
|
||||
/** Check if new message has arrived */
|
||||
#define CO_FLAG_READ(rxNew) ((rxNew) != NULL)
|
||||
/** Set new message flag */
|
||||
#define CO_FLAG_SET(rxNew) \
|
||||
{ \
|
||||
__sync_synchronize(); \
|
||||
rxNew = (void*)1L; \
|
||||
}
|
||||
/** Clear new message flag */
|
||||
#define CO_FLAG_CLEAR(rxNew) \
|
||||
{ \
|
||||
__sync_synchronize(); \
|
||||
rxNew = NULL; \
|
||||
}
|
||||
|
||||
/** @} */
|
||||
#endif /* CO_DOXYGEN */
|
||||
|
||||
/**
|
||||
* @defgroup CO_Default_CAN_ID_t Default CANopen identifiers
|
||||
* @{
|
||||
*
|
||||
* Default CANopen identifiers for CANopen communication objects. Same as 11-bit addresses of CAN messages. These are
|
||||
* default identifiers and can be changed in CANopen. Especially PDO identifiers are configured in PDO linking phase of
|
||||
* the CANopen network configuration.
|
||||
*/
|
||||
#define CO_CAN_ID_NMT_SERVICE 0x000U /**< 0x000 Network management */
|
||||
#define CO_CAN_ID_GFC 0x001U /**< 0x001 Global fail-safe command */
|
||||
#define CO_CAN_ID_SYNC 0x080U /**< 0x080 Synchronous message */
|
||||
#define CO_CAN_ID_EMERGENCY 0x080U /**< 0x080 Emergency messages (+nodeID) */
|
||||
#define CO_CAN_ID_TIME 0x100U /**< 0x100 Time message */
|
||||
#define CO_CAN_ID_SRDO_1 0x0FFU /**< 0x0FF Default SRDO1 (+2*nodeID) */
|
||||
#define CO_CAN_ID_TPDO_1 0x180U /**< 0x180 Default TPDO1 (+nodeID) */
|
||||
#define CO_CAN_ID_RPDO_1 0x200U /**< 0x200 Default RPDO1 (+nodeID) */
|
||||
#define CO_CAN_ID_TPDO_2 0x280U /**< 0x280 Default TPDO2 (+nodeID) */
|
||||
#define CO_CAN_ID_RPDO_2 0x300U /**< 0x300 Default RPDO2 (+nodeID) */
|
||||
#define CO_CAN_ID_TPDO_3 0x380U /**< 0x380 Default TPDO3 (+nodeID) */
|
||||
#define CO_CAN_ID_RPDO_3 0x400U /**< 0x400 Default RPDO3 (+nodeID) */
|
||||
#define CO_CAN_ID_TPDO_4 0x480U /**< 0x480 Default TPDO4 (+nodeID) */
|
||||
#define CO_CAN_ID_RPDO_4 0x500U /**< 0x500 Default RPDO5 (+nodeID) */
|
||||
#define CO_CAN_ID_SDO_SRV 0x580U /**< 0x580 SDO response from server (+nodeID) */
|
||||
#define CO_CAN_ID_SDO_CLI 0x600U /**< 0x600 SDO request from client (+nodeID) */
|
||||
#define CO_CAN_ID_HEARTBEAT 0x700U /**< 0x700 Heartbeat message */
|
||||
#define CO_CAN_ID_LSS_SLV 0x7E4U /**< 0x7E4 LSS response from slave */
|
||||
#define CO_CAN_ID_LSS_MST 0x7E5U /**< 0x7E5 LSS request from master */
|
||||
|
||||
/** @} */ /* CO_Default_CAN_ID_t */
|
||||
|
||||
/**
|
||||
* Restricted CAN-IDs
|
||||
*
|
||||
* Macro for verifying 'Restricted CAN-IDs', as specified by standard CiA301. They shall not be used for SYNC, TIME,
|
||||
* EMCY, PDO and SDO.
|
||||
*/
|
||||
#ifndef CO_IS_RESTRICTED_CAN_ID
|
||||
#define CO_IS_RESTRICTED_CAN_ID(CAN_ID) \
|
||||
(((CAN_ID) <= 0x7FU) || (((CAN_ID) >= 0x101U) && ((CAN_ID) <= 0x180U)) \
|
||||
|| (((CAN_ID) >= 0x581U) && ((CAN_ID) <= 0x5FFU)) || (((CAN_ID) >= 0x601U) && ((CAN_ID) <= 0x67FU)) \
|
||||
|| (((CAN_ID) >= 0x6E0U) && ((CAN_ID) <= 0x6FFU)) || ((CAN_ID) >= 0x701U))
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_CAN_ERR_status_t CAN error status bitmasks
|
||||
* @{
|
||||
*
|
||||
* CAN warning level is reached, if CAN transmit or receive error counter is more or equal to 96. CAN passive level is
|
||||
* reached, if counters are more or equal to 128. Transmitter goes in error state 'bus off' if transmit error counter is
|
||||
* more or equal to 256.
|
||||
*/
|
||||
#define CO_CAN_ERRTX_WARNING 0x0001U /**< 0x0001 CAN transmitter warning */
|
||||
#define CO_CAN_ERRTX_PASSIVE 0x0002U /**< 0x0002 CAN transmitter passive */
|
||||
#define CO_CAN_ERRTX_BUS_OFF 0x0004U /**< 0x0004 CAN transmitter bus off */
|
||||
#define CO_CAN_ERRTX_OVERFLOW 0x0008U /**< 0x0008 CAN transmitter overflow */
|
||||
#define CO_CAN_ERRTX_PDO_LATE 0x0080U /**< 0x0080 TPDO is outside sync window */
|
||||
#define CO_CAN_ERRRX_WARNING 0x0100U /**< 0x0100 CAN receiver warning */
|
||||
#define CO_CAN_ERRRX_PASSIVE 0x0200U /**< 0x0200 CAN receiver passive */
|
||||
#define CO_CAN_ERRRX_OVERFLOW 0x0800U /**< 0x0800 CAN receiver overflow */
|
||||
#define CO_CAN_ERR_WARN_PASSIVE 0x0303U /**< 0x0303 combination */
|
||||
|
||||
/** @} */ /* CO_CAN_ERR_status_t */
|
||||
|
||||
/**
|
||||
* Return values of some CANopen functions. If function was executed successfully it returns 0 otherwise it returns <0.
|
||||
*/
|
||||
typedef enum {
|
||||
CO_ERROR_NO = 0, /**< Operation completed successfully */
|
||||
CO_ERROR_ILLEGAL_ARGUMENT = -1, /**< Error in function arguments */
|
||||
CO_ERROR_OUT_OF_MEMORY = -2, /**< Memory allocation failed */
|
||||
CO_ERROR_TIMEOUT = -3, /**< Function timeout */
|
||||
CO_ERROR_ILLEGAL_BAUDRATE = -4, /**< Illegal baudrate passed to function CO_CANmodule_init() */
|
||||
CO_ERROR_RX_OVERFLOW = -5, /**< Previous message was not processed yet */
|
||||
CO_ERROR_RX_PDO_OVERFLOW = -6, /**< previous PDO was not processed yet */
|
||||
CO_ERROR_RX_MSG_LENGTH = -7, /**< Wrong receive message length */
|
||||
CO_ERROR_RX_PDO_LENGTH = -8, /**< Wrong receive PDO length */
|
||||
CO_ERROR_TX_OVERFLOW = -9, /**< Previous message is still waiting, buffer full */
|
||||
CO_ERROR_TX_PDO_WINDOW = -10, /**< Synchronous TPDO is outside window */
|
||||
CO_ERROR_TX_UNCONFIGURED = -11, /**< Transmit buffer was not configured properly */
|
||||
CO_ERROR_OD_PARAMETERS = -12, /**< Error in Object Dictionary parameters */
|
||||
CO_ERROR_DATA_CORRUPT = -13, /**< Stored data are corrupt */
|
||||
CO_ERROR_CRC = -14, /**< CRC does not match */
|
||||
CO_ERROR_TX_BUSY = -15, /**< Sending rejected because driver is busy. Try again */
|
||||
CO_ERROR_WRONG_NMT_STATE = -16, /**< Command can't be processed in current state */
|
||||
CO_ERROR_SYSCALL = -17, /**< Syscall failed */
|
||||
CO_ERROR_INVALID_STATE = -18, /**< Driver not ready */
|
||||
CO_ERROR_NODE_ID_UNCONFIGURED_LSS =
|
||||
-19 /**< Node-id is in LSS unconfigured state. If objects are handled properly, this may not be an error. */
|
||||
} CO_ReturnError_t;
|
||||
|
||||
/**
|
||||
* Request CAN configuration (stopped) mode and *wait* until it is set.
|
||||
*
|
||||
* @param CANptr Pointer to CAN device
|
||||
*/
|
||||
void CO_CANsetConfigurationMode(void* CANptr);
|
||||
|
||||
/**
|
||||
* Request CAN normal (operational) mode and *wait* until it is set.
|
||||
*
|
||||
* @param CANmodule CO_CANmodule_t object.
|
||||
*/
|
||||
void CO_CANsetNormalMode(CO_CANmodule_t* CANmodule);
|
||||
|
||||
/**
|
||||
* Initialize CAN module object.
|
||||
*
|
||||
* Function must be called in the communication reset section. CAN module must be in Configuration Mode before.
|
||||
*
|
||||
* @param CANmodule This object will be initialized.
|
||||
* @param CANptr Pointer to CAN device.
|
||||
* @param rxArray Array for handling received CAN messages
|
||||
* @param rxSize Size of the above array. Must be equal to number of receiving CAN objects.
|
||||
* @param txArray Array for handling transmitting CAN messages
|
||||
* @param txSize Size of the above array. Must be equal to number of transmitting CAN objects.
|
||||
* @param CANbitRate Valid values are (in kbps): 10, 20, 50, 125, 250, 500, 800, 1000. If value is illegal, bitrate
|
||||
* defaults to 125.
|
||||
*
|
||||
* Return #CO_ReturnError_t: CO_ERROR_NO or CO_ERROR_ILLEGAL_ARGUMENT.
|
||||
*/
|
||||
CO_ReturnError_t CO_CANmodule_init(CO_CANmodule_t* CANmodule, void* CANptr, CO_CANrx_t rxArray[], uint16_t rxSize,
|
||||
CO_CANtx_t txArray[], uint16_t txSize, uint16_t CANbitRate);
|
||||
|
||||
/**
|
||||
* Switch off CANmodule. Call at program exit.
|
||||
*
|
||||
* @param CANmodule CAN module object.
|
||||
*/
|
||||
void CO_CANmodule_disable(CO_CANmodule_t* CANmodule);
|
||||
|
||||
/**
|
||||
* Configure CAN message receive buffer.
|
||||
*
|
||||
* Function configures specific CAN receive buffer. It sets CAN identifier and connects buffer with specific object.
|
||||
* Function must be called for each member in _rxArray_ from CO_CANmodule_t.
|
||||
*
|
||||
* @param CANmodule This object.
|
||||
* @param index Index of the specific buffer in _rxArray_.
|
||||
* @param ident 11-bit standard CAN Identifier. If two or more CANrx buffers have the same _ident_, then buffer with
|
||||
* lowest _index_ has precedence and other CANrx buffers will be ignored.
|
||||
* @param mask 11-bit mask for identifier. Most usually set to 0x7FF. Received message (rcvMsg) will be accepted if the
|
||||
* following condition is true: (((rcvMsgId ^ ident) & mask) == 0).
|
||||
* @param rtr If true, 'Remote Transmit Request' messages will be accepted.
|
||||
* @param object CANopen object, to which buffer is connected. It will be used as an argument to CANrx_callback. Its
|
||||
* type is (void), CANrx_callback will change its type back to the correct object type.
|
||||
* @param CANrx_callback Pointer to function, which will be called, if received CAN message matches the identifier. It
|
||||
* must be fast function.
|
||||
*
|
||||
* Return #CO_ReturnError_t: CO_ERROR_NO CO_ERROR_ILLEGAL_ARGUMENT or CO_ERROR_OUT_OF_MEMORY (not enough masks for
|
||||
* configuration).
|
||||
*/
|
||||
CO_ReturnError_t CO_CANrxBufferInit(CO_CANmodule_t* CANmodule, uint16_t index, uint16_t ident, uint16_t mask,
|
||||
bool_t rtr, void* object, void (*CANrx_callback)(void* object, void* message));
|
||||
|
||||
/**
|
||||
* Configure CAN message transmit buffer.
|
||||
*
|
||||
* Function configures specific CAN transmit buffer. Function must be called for each member in _txArray_ from
|
||||
* CO_CANmodule_t.
|
||||
*
|
||||
* @param CANmodule This object.
|
||||
* @param index Index of the specific buffer in _txArray_.
|
||||
* @param ident 11-bit standard CAN Identifier.
|
||||
* @param rtr If true, 'Remote Transmit Request' messages will be transmitted.
|
||||
* @param noOfBytes Length of CAN message in bytes (0 to 8 bytes).
|
||||
* @param syncFlag This flag bit is used for synchronous TPDO messages. If it is set, message will not be sent, if
|
||||
* current time is outside synchronous window.
|
||||
*
|
||||
* @return Pointer to CAN transmit message buffer. 8 bytes data array inside buffer should be written, before
|
||||
* CO_CANsend() function is called. Zero is returned in case of wrong arguments.
|
||||
*/
|
||||
CO_CANtx_t* CO_CANtxBufferInit(CO_CANmodule_t* CANmodule, uint16_t index, uint16_t ident, bool_t rtr, uint8_t noOfBytes,
|
||||
bool_t syncFlag);
|
||||
|
||||
/**
|
||||
* Send CAN message.
|
||||
*
|
||||
* @param CANmodule This object.
|
||||
* @param buffer Pointer to transmit buffer, returned by CO_CANtxBufferInit(). Data bytes must be written in buffer
|
||||
* before function call.
|
||||
*
|
||||
* @return #CO_ReturnError_t: CO_ERROR_NO, CO_ERROR_TX_OVERFLOW or CO_ERROR_TX_PDO_WINDOW (Synchronous TPDO is outside
|
||||
* window).
|
||||
*/
|
||||
CO_ReturnError_t CO_CANsend(CO_CANmodule_t* CANmodule, CO_CANtx_t* buffer);
|
||||
|
||||
/**
|
||||
* Clear all synchronous TPDOs from CAN module transmit buffers.
|
||||
*
|
||||
* CANopen allows synchronous PDO communication only inside time between SYNC message and SYNC Window. If time is
|
||||
* outside this window, new synchronous PDOs must not be sent and all pending sync TPDOs, which may be on CAN TX
|
||||
* buffers, may optionally be cleared.
|
||||
*
|
||||
* This function checks (and aborts transmission if necessary) CAN TX buffers when it is called. Function should be
|
||||
* called by the stack in the moment, when SYNC time was just passed out of synchronous window.
|
||||
*
|
||||
* @param CANmodule This object.
|
||||
*/
|
||||
void CO_CANclearPendingSyncPDOs(CO_CANmodule_t* CANmodule);
|
||||
|
||||
/**
|
||||
* Process can module - verify CAN errors
|
||||
*
|
||||
* Function must be called cyclically. It should calculate CANerrorStatus bitfield for CAN errors defined in @ref
|
||||
* CO_CAN_ERR_status_t.
|
||||
*
|
||||
* @param CANmodule This object.
|
||||
*/
|
||||
void CO_CANmodule_process(CO_CANmodule_t* CANmodule);
|
||||
|
||||
/**
|
||||
* Get uint8_t value from memory buffer
|
||||
*
|
||||
* @param buf Memory buffer to get value from.
|
||||
*
|
||||
* @return Value
|
||||
*/
|
||||
static inline uint8_t
|
||||
CO_getUint8(const void* buf) {
|
||||
uint8_t value;
|
||||
(void)memmove((void*)&value, buf, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Get uint16_t value from memory buffer, see @ref CO_getUint8 */
|
||||
static inline uint16_t
|
||||
CO_getUint16(const void* buf) {
|
||||
uint16_t value;
|
||||
(void)memmove((void*)&value, buf, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Get uint32_t value from memory buffer, see @ref CO_getUint8 */
|
||||
static inline uint32_t
|
||||
CO_getUint32(const void* buf) {
|
||||
uint32_t value;
|
||||
(void)memmove((void*)&value, buf, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write uint8_t value into memory buffer
|
||||
*
|
||||
* @param buf Memory buffer.
|
||||
* @param value Value to be written into buf.
|
||||
*
|
||||
* @return number of bytes written.
|
||||
*/
|
||||
static inline uint8_t
|
||||
CO_setUint8(void* buf, uint8_t value) {
|
||||
(void)memmove(buf, (const void*)&value, sizeof(value));
|
||||
return (uint8_t)(sizeof(value));
|
||||
}
|
||||
|
||||
/** Write uint16_t value into memory buffer, see @ref CO_setUint8 */
|
||||
static inline uint8_t
|
||||
CO_setUint16(void* buf, uint16_t value) {
|
||||
(void)memmove(buf, (const void*)&value, sizeof(value));
|
||||
return (uint8_t)(sizeof(value));
|
||||
}
|
||||
|
||||
/** Write uint32_t value into memory buffer, see @ref CO_setUint8 */
|
||||
static inline uint8_t
|
||||
CO_setUint32(void* buf, uint32_t value) {
|
||||
(void)memmove(buf, (const void*)&value, sizeof(value));
|
||||
return (uint8_t)(sizeof(value));
|
||||
}
|
||||
|
||||
/** @} */ /* CO_driver */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CO_DRIVER_H */
|
||||
1493
Middleware/CANopenNode/301/CO_fifo.c
Normal file
1493
Middleware/CANopenNode/301/CO_fifo.c
Normal file
File diff suppressed because it is too large
Load Diff
484
Middleware/CANopenNode/301/CO_fifo.h
Normal file
484
Middleware/CANopenNode/301/CO_fifo.h
Normal file
@@ -0,0 +1,484 @@
|
||||
/**
|
||||
* FIFO circular buffer
|
||||
*
|
||||
* @file CO_fifo.h
|
||||
* @ingroup CO_CANopen_301_fifo
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2020 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.
|
||||
*/
|
||||
|
||||
#ifndef CO_FIFO_H
|
||||
#define CO_FIFO_H
|
||||
|
||||
#include "301/CO_driver.h"
|
||||
|
||||
/* default configuration, see CO_config.h */
|
||||
#ifndef CO_CONFIG_FIFO
|
||||
#define CO_CONFIG_FIFO (0)
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_FIFO)&CO_CONFIG_FIFO_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_CANopen_301_fifo FIFO circular buffer FIFO circular buffer for continuous data flow.
|
||||
*
|
||||
* @ingroup CO_CANopen_301
|
||||
* @{
|
||||
* FIFO is organized as circular buffer with predefined capacity. It must be initialized by CO_fifo_init(). Functions
|
||||
* are not not thread safe.
|
||||
*
|
||||
* It can be used as general purpose FIFO circular buffer for any data. Data can be written by CO_fifo_write() and read
|
||||
* by CO_fifo_read() functions.
|
||||
*
|
||||
* Buffer has additional functions for usage with CiA309-3 standard. It acts as circular buffer for storing ascii
|
||||
* commands and fetching tokens from them.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fifo object
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t* buf; /**< Buffer of size bufSize. Initialized by CO_fifo_init() */
|
||||
size_t bufSize; /**< Initialized by CO_fifo_init() */
|
||||
size_t writePtr; /**< Location in the buffer, which will be next written. */
|
||||
size_t readPtr; /**< Location in the buffer, which will be next read. */
|
||||
#if (((CO_CONFIG_FIFO)&CO_CONFIG_FIFO_ALT_READ) != 0) || defined CO_DOXYGEN
|
||||
size_t altReadPtr; /**< Location in the buffer, which will be next read. */
|
||||
#endif
|
||||
#if (((CO_CONFIG_FIFO)&CO_CONFIG_FIFO_ASCII_DATATYPES) != 0) || defined CO_DOXYGEN
|
||||
bool_t started; /**< helper variable, set to false in CO_fifo_reset(), used in some functions. */
|
||||
uint32_t aux; /**< auxiliary variable, used in some functions. */
|
||||
#endif
|
||||
} CO_fifo_t;
|
||||
|
||||
/**
|
||||
* Initialize fifo object
|
||||
*
|
||||
* @param fifo This object will be initialized
|
||||
* @param buf Pointer to externally defined buffer
|
||||
* @param bufSize Size of the above buffer. Usable size of the buffer will be one byte less than bufSize, it is used for
|
||||
* operation of the circular buffer.
|
||||
*/
|
||||
void CO_fifo_init(CO_fifo_t* fifo, uint8_t* buf, size_t bufSize);
|
||||
|
||||
/**
|
||||
* Reset fifo object, make it empty
|
||||
*
|
||||
* @param fifo This object
|
||||
*/
|
||||
static inline void
|
||||
CO_fifo_reset(CO_fifo_t* fifo) {
|
||||
if (fifo != NULL) {
|
||||
fifo->readPtr = 0;
|
||||
fifo->writePtr = 0;
|
||||
#if ((CO_CONFIG_FIFO)&CO_CONFIG_FIFO_ASCII_DATATYPES) != 0
|
||||
fifo->started = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge all data in fifo object, keep other properties
|
||||
*
|
||||
* @param fifo This object
|
||||
*
|
||||
* @return true, if data were purged or false, if fifo were already empty
|
||||
*/
|
||||
static inline bool_t
|
||||
CO_fifo_purge(CO_fifo_t* fifo) {
|
||||
if (fifo != NULL && fifo->readPtr != fifo->writePtr) {
|
||||
fifo->readPtr = 0;
|
||||
fifo->writePtr = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get free buffer space in CO_fifo_t object
|
||||
*
|
||||
* @param fifo This object
|
||||
*
|
||||
* @return number of available bytes
|
||||
*/
|
||||
static inline size_t
|
||||
CO_fifo_getSpace(CO_fifo_t* fifo) {
|
||||
int sizeLeft = (int)fifo->readPtr - (int)fifo->writePtr - 1;
|
||||
if (sizeLeft < 0) {
|
||||
sizeLeft += (int)fifo->bufSize;
|
||||
}
|
||||
|
||||
return (size_t)sizeLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get size of data inside CO_fifo_t buffer object
|
||||
*
|
||||
* @param fifo This object
|
||||
*
|
||||
* @return number of occupied bytes
|
||||
*/
|
||||
static inline size_t
|
||||
CO_fifo_getOccupied(CO_fifo_t* fifo) {
|
||||
int sizeOccupied = (int)fifo->writePtr - (int)fifo->readPtr;
|
||||
if (sizeOccupied < 0) {
|
||||
sizeOccupied += (int)fifo->bufSize;
|
||||
}
|
||||
|
||||
return (size_t)sizeOccupied;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put one character into CO_fifo_t buffer object
|
||||
*
|
||||
* @param fifo This object
|
||||
* @param c Character to put
|
||||
*
|
||||
* @return true, if write was successful (enough space in fifo buffer)
|
||||
*/
|
||||
static inline bool_t
|
||||
CO_fifo_putc(CO_fifo_t* fifo, const uint8_t c) {
|
||||
if (fifo != NULL && fifo->buf != NULL) {
|
||||
size_t writePtrNext = fifo->writePtr + 1;
|
||||
if (writePtrNext != fifo->readPtr && !(writePtrNext == fifo->bufSize && fifo->readPtr == 0)) {
|
||||
fifo->buf[fifo->writePtr] = c;
|
||||
fifo->writePtr = (writePtrNext == fifo->bufSize) ? 0 : writePtrNext;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put one character into CO_fifo_t buffer object
|
||||
*
|
||||
* Overwrite old characters, if run out of space
|
||||
*
|
||||
* @param fifo This object
|
||||
* @param c Character to put
|
||||
*/
|
||||
static inline void
|
||||
CO_fifo_putc_ov(CO_fifo_t* fifo, const uint8_t c) {
|
||||
if (fifo != NULL && fifo->buf != NULL) {
|
||||
fifo->buf[fifo->writePtr] = c;
|
||||
|
||||
if (++fifo->writePtr == fifo->bufSize) {
|
||||
fifo->writePtr = 0;
|
||||
}
|
||||
if (fifo->readPtr == fifo->writePtr) {
|
||||
if (++fifo->readPtr == fifo->bufSize) {
|
||||
fifo->readPtr = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get one character from CO_fifo_t buffer object
|
||||
*
|
||||
* @param fifo This object
|
||||
* @param buf Buffer of length one byte, where character will be copied
|
||||
*
|
||||
* @return true, if read was successful (non-empty fifo buffer)
|
||||
*/
|
||||
static inline bool_t
|
||||
CO_fifo_getc(CO_fifo_t* fifo, uint8_t* buf) {
|
||||
if (fifo != NULL && buf != NULL && fifo->readPtr != fifo->writePtr) {
|
||||
*buf = fifo->buf[fifo->readPtr];
|
||||
if (++fifo->readPtr == fifo->bufSize) {
|
||||
fifo->readPtr = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write data into CO_fifo_t object.
|
||||
*
|
||||
* This function copies data from buf into internal buffer of CO_fifo_t object. Function returns number of bytes
|
||||
* successfully copied. If there is not enough space in destination, not all bytes will be copied.
|
||||
*
|
||||
* @param fifo This object
|
||||
* @param buf Buffer which will be copied
|
||||
* @param count Number of bytes in buf
|
||||
* @param [in,out] crc Externally defined variable for CRC checksum, ignored if NULL
|
||||
*
|
||||
* @return number of bytes actually written.
|
||||
*/
|
||||
size_t CO_fifo_write(CO_fifo_t* fifo, const uint8_t* buf, size_t count, uint16_t* crc);
|
||||
|
||||
/**
|
||||
* Read data from CO_fifo_t object.
|
||||
*
|
||||
* This function copies data from internal buffer of CO_fifo_t object into buf. Function returns number of bytes
|
||||
* successfully copied. Function also writes true into eof argument, if command delimiter character is reached.
|
||||
*
|
||||
* @param fifo This object
|
||||
* @param buf Buffer into which data will be copied
|
||||
* @param count Copy up to count bytes into buffer
|
||||
* @param [out] eof If different than NULL, then search for delimiter character. If found, then read up to (including)
|
||||
* that character and set *eof to true. Otherwise set *eof to false.
|
||||
*
|
||||
* @return number of bytes actually read.
|
||||
*/
|
||||
size_t CO_fifo_read(CO_fifo_t* fifo, uint8_t* buf, size_t count, bool_t* eof);
|
||||
|
||||
#if (((CO_CONFIG_FIFO)&CO_CONFIG_FIFO_ALT_READ) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Initializes alternate read with #CO_fifo_altRead
|
||||
*
|
||||
* @param fifo This object
|
||||
* @param offset Offset in bytes from original read pointer
|
||||
*
|
||||
* @return same as offset or lower, if there is not enough data.
|
||||
*/
|
||||
size_t CO_fifo_altBegin(CO_fifo_t* fifo, size_t offset);
|
||||
|
||||
/**
|
||||
* Ends alternate read with #CO_fifo_altRead and calculate crc checksum
|
||||
*
|
||||
* @param fifo This object
|
||||
* @param [in,out] crc Externally defined variable for CRC checksum, ignored if NULL
|
||||
*/
|
||||
void CO_fifo_altFinish(CO_fifo_t* fifo, uint16_t* crc);
|
||||
|
||||
/**
|
||||
* Get alternate size of remaining data, see #CO_fifo_altRead
|
||||
*
|
||||
* @param fifo This object
|
||||
*
|
||||
* @return number of occupied bytes.
|
||||
*/
|
||||
static inline size_t
|
||||
CO_fifo_altGetOccupied(CO_fifo_t* fifo) {
|
||||
int sizeOccupied = (int)fifo->writePtr - (int)fifo->altReadPtr;
|
||||
if (sizeOccupied < 0) {
|
||||
sizeOccupied += (int)fifo->bufSize;
|
||||
}
|
||||
|
||||
return (size_t)sizeOccupied;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternate read data from CO_fifo_t object.
|
||||
*
|
||||
* This function is similar as CO_fifo_read(), but uses alternate read pointer inside circular buffer. It reads data
|
||||
* from the buffer and data remains in it. This function uses alternate read pointer and keeps original read pointer
|
||||
* unchanged. Before using this function, alternate read pointer must be initialized with CO_fifo_altBegin().
|
||||
* CO_fifo_altFinish() sets original read pointer to alternate read pointer and so empties the buffer.
|
||||
*
|
||||
* @param fifo This object
|
||||
* @param buf Buffer into which data will be copied
|
||||
* @param count Copy up to count bytes into buffer
|
||||
*
|
||||
* @return number of bytes actually read.
|
||||
*/
|
||||
size_t CO_fifo_altRead(CO_fifo_t* fifo, uint8_t* buf, size_t count);
|
||||
#endif /* #if (CO_CONFIG_FIFO) & CO_CONFIG_FIFO_ALT_READ */
|
||||
|
||||
#if (((CO_CONFIG_FIFO)&CO_CONFIG_FIFO_ASCII_COMMANDS) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Search command inside FIFO
|
||||
*
|
||||
* If there are some data inside the FIFO, then search for command delimiter.
|
||||
*
|
||||
* If command is long, then in the buffer may not be enough space for it. In that case buffer is full and no delimiter
|
||||
* is present. Function then returns true and command should be processed for the starting tokens. Buffer can later be
|
||||
* refilled multiple times, until command is closed by command delimiter.
|
||||
*
|
||||
* If this function returns different than 0, then buffer is usually read by multiple CO_fifo_readToken() calls. If
|
||||
* reads was successful, then delimiter is reached and fifo->readPtr is set after the command. If any
|
||||
* CO_fifo_readToken() returns nonzero *err, then there is an error and command should be cleared. All this procedure
|
||||
* must be implemented inside single function call.
|
||||
*
|
||||
* @param fifo This object.
|
||||
* @param clear If true, then command will be cleared from the buffer. If there is no delimiter, buffer will be cleared
|
||||
* entirely.
|
||||
*
|
||||
* @return true if command with delimiter found or buffer full.
|
||||
*/
|
||||
bool_t CO_fifo_CommSearch(CO_fifo_t* fifo, bool_t clear);
|
||||
|
||||
/**
|
||||
* Trim spaces inside FIFO
|
||||
*
|
||||
* Function removes all non graphical characters and comments from fifo buffer. It stops on first graphical character or
|
||||
* on command delimiter (later is also removed).
|
||||
*
|
||||
* @param fifo This object.
|
||||
* @param [in, out] insideComment if set to true as input, it skips all characters and searches only for delimiter. As
|
||||
* output it is set to true, if fifo is empty, is inside comment and command delimiter is not found.
|
||||
*
|
||||
* @return true if command delimiter was found.
|
||||
*/
|
||||
bool_t CO_fifo_trimSpaces(CO_fifo_t* fifo, bool_t* insideComment);
|
||||
|
||||
/**
|
||||
* Get token from FIFO buffer
|
||||
*
|
||||
* Function search FIFO buffer for token. Token is string of only graphical characters. Graphical character is any
|
||||
* printable character except space with acsii code within limits: 0x20 < code < 0x7F (see isgraph() function).
|
||||
*
|
||||
* If token is found, then copy it to the buf, if count is large enough. On success also set readPtr to point to the
|
||||
* next graphical character.
|
||||
*
|
||||
* Each token must have at least one empty space after it (space, command delimiter, '\0', etc.). Delimiter must not be
|
||||
* graphical character.
|
||||
*
|
||||
* If comment delimiter (delimComment, see #CO_fifo_init) character is found, then all string till command delimiter
|
||||
* (delimCommand, see #CO_fifo_init) will be erased from the buffer.
|
||||
*
|
||||
* See also #CO_fifo_CommSearch().
|
||||
*
|
||||
* @param fifo This object.
|
||||
* @param buf Buffer into which data will be copied. Will be terminated by '\0'.
|
||||
* @param count Copy up to count bytes into buffer
|
||||
* @param [in,out] closed This is input/output variable. Not used if NULL.
|
||||
* - As output variable it is set to 1, if command delimiter (delimCommand, see #CO_fifo_init) is found after the token
|
||||
* and set to 0 otherwise.
|
||||
* - As input variable it is used for verifying error condition:
|
||||
* - *closed = 0: Set *err to true if token is empty or command delimiter is found.
|
||||
* - *closed = 1: Set *err to true if token is empty or command delimiter is NOT found.
|
||||
* - *closed = any other value: No checking of token size or command delimiter.
|
||||
* @param [out] err If not NULL, it is set to true if token is larger than buf or in matching combination in 'closed'
|
||||
* argument. If it is already true, then function returns immediately.
|
||||
*
|
||||
* @return Number of bytes read.
|
||||
*/
|
||||
size_t CO_fifo_readToken(CO_fifo_t* fifo, char* buf, size_t count, uint8_t* closed, bool_t* err);
|
||||
#endif /* (CO_CONFIG_FIFO) & CO_CONFIG_FIFO_ASCII_COMMANDS */
|
||||
|
||||
#if (((CO_CONFIG_FIFO)&CO_CONFIG_FIFO_ASCII_DATATYPES) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Read uint8_t variable from fifo and output as ascii string.
|
||||
*
|
||||
* @param fifo This object.
|
||||
* @param buf Buffer into which ascii string will be copied.
|
||||
* @param count Available count of bytes inside the buf.
|
||||
* @param end True indicates, that fifo contains last bytes of data.
|
||||
*
|
||||
* @return Number of ascii bytes written into buf.
|
||||
*/
|
||||
size_t CO_fifo_readU82a(CO_fifo_t* fifo, char* buf, size_t count, bool_t end);
|
||||
/** Read uint16_t variable from fifo as ascii string, see CO_fifo_readU82a */
|
||||
size_t CO_fifo_readU162a(CO_fifo_t* fifo, char* buf, size_t count, bool_t end);
|
||||
/** Read uint32_t variable from fifo as ascii string, see CO_fifo_readU82a */
|
||||
size_t CO_fifo_readU322a(CO_fifo_t* fifo, char* buf, size_t count, bool_t end);
|
||||
/** Read uint64_t variable from fifo as ascii string, see CO_fifo_readU82a */
|
||||
size_t CO_fifo_readU642a(CO_fifo_t* fifo, char* buf, size_t count, bool_t end);
|
||||
/** Read uint8_t variable from fifo as ascii string, see CO_fifo_readU82a */
|
||||
size_t CO_fifo_readX82a(CO_fifo_t* fifo, char* buf, size_t count, bool_t end);
|
||||
/** Read uint16_t variable from fifo as ascii string, see CO_fifo_readU82a */
|
||||
size_t CO_fifo_readX162a(CO_fifo_t* fifo, char* buf, size_t count, bool_t end);
|
||||
/** Read uint32_t variable from fifo as ascii string, see CO_fifo_readU82a */
|
||||
size_t CO_fifo_readX322a(CO_fifo_t* fifo, char* buf, size_t count, bool_t end);
|
||||
/** Read uint64_t variable from fifo as ascii string, see CO_fifo_readU82a */
|
||||
size_t CO_fifo_readX642a(CO_fifo_t* fifo, char* buf, size_t count, bool_t end);
|
||||
/** Read int8_t variable from fifo as ascii string, see CO_fifo_readU82a */
|
||||
size_t CO_fifo_readI82a(CO_fifo_t* fifo, char* buf, size_t count, bool_t end);
|
||||
/** Read int16_t variable from fifo as ascii string, see CO_fifo_readU82a */
|
||||
size_t CO_fifo_readI162a(CO_fifo_t* fifo, char* buf, size_t count, bool_t end);
|
||||
/** Read int32_t variable from fifo as ascii string, see CO_fifo_readU82a */
|
||||
size_t CO_fifo_readI322a(CO_fifo_t* fifo, char* buf, size_t count, bool_t end);
|
||||
/** Read int64_t variable from fifo as ascii string, see CO_fifo_readU82a */
|
||||
size_t CO_fifo_readI642a(CO_fifo_t* fifo, char* buf, size_t count, bool_t end);
|
||||
/** Read float32_t variable from fifo as ascii string, see CO_fifo_readU82a */
|
||||
size_t CO_fifo_readR322a(CO_fifo_t* fifo, char* buf, size_t count, bool_t end);
|
||||
/** Read float64_t variable from fifo as ascii string, see CO_fifo_readU82a */
|
||||
size_t CO_fifo_readR642a(CO_fifo_t* fifo, char* buf, size_t count, bool_t end);
|
||||
/** Read data from fifo and output as space separated two digit ascii string,
|
||||
* see also CO_fifo_readU82a */
|
||||
size_t CO_fifo_readHex2a(CO_fifo_t* fifo, char* buf, size_t count, bool_t end);
|
||||
/** Read data from fifo and output as visible string. A visible string is enclosed with double quotes. If a double quote
|
||||
* is used within the string, the quotes are escaped by a second quotes, e.g. “Hello “”World””, CANopen is great”. UTF-8
|
||||
* characters and also line breaks works with this function. Function removes all NULL and CR characters from output
|
||||
* string. See also CO_fifo_readU82a */
|
||||
size_t CO_fifo_readVs2a(CO_fifo_t* fifo, char* buf, size_t count, bool_t end);
|
||||
/** Read data from fifo and output as mime-base64 encoded string. Encoding is as specified in RFC 2045, without CR-LF,
|
||||
* but one long string. See also CO_fifo_readU82a */
|
||||
size_t CO_fifo_readB642a(CO_fifo_t* fifo, char* buf, size_t count, bool_t end);
|
||||
|
||||
/**
|
||||
* @defgroup uint8_t Bitfields for status argument from CO_fifo_cpyTok2U8 function and similar
|
||||
* @{
|
||||
*/
|
||||
#define CO_fifo_st_closed 0x01U /**< Bit is set, if command delimiter is reached in src */
|
||||
#define CO_fifo_st_partial \
|
||||
0x02U /**< Bit is set, if copy was partial and more data are available. If unset and no error, then all data was \
|
||||
successfully copied. */
|
||||
#define CO_fifo_st_errTok 0x10U /**< Bit is set, if no valid token found */
|
||||
#define CO_fifo_st_errVal 0x20U /**< Bit is set, if value is not valid or out of limits */
|
||||
#define CO_fifo_st_errBuf 0x40U /**< Bit is set, if destination buffer is to small */
|
||||
#define CO_fifo_st_errInt 0x80U /**< Bit is set, if internal error */
|
||||
#define CO_fifo_st_errMask 0xF0U /**< Bitmask for error bits */
|
||||
/** @} */ /* uint8_t Bitfields */
|
||||
|
||||
/**
|
||||
* Read ascii string from src fifo and copy as uint8_t variable to dest fifo.
|
||||
*
|
||||
* @param dest destination fifo buffer object.
|
||||
* @param src source fifo buffer object.
|
||||
* @param [out] status bitfield of the uint8_t type.
|
||||
*
|
||||
* @return Number of bytes written into dest.
|
||||
*/
|
||||
size_t CO_fifo_cpyTok2U8(CO_fifo_t* dest, CO_fifo_t* src, uint8_t* status);
|
||||
/** Copy ascii string to uint16_t variable, see CO_fifo_cpyTok2U8 */
|
||||
size_t CO_fifo_cpyTok2U16(CO_fifo_t* dest, CO_fifo_t* src, uint8_t* status);
|
||||
/** Copy ascii string to uint32_t variable, see CO_fifo_cpyTok2U8 */
|
||||
size_t CO_fifo_cpyTok2U32(CO_fifo_t* dest, CO_fifo_t* src, uint8_t* status);
|
||||
/** Copy ascii string to uint64_t variable, see CO_fifo_cpyTok2U8 */
|
||||
size_t CO_fifo_cpyTok2U64(CO_fifo_t* dest, CO_fifo_t* src, uint8_t* status);
|
||||
/** Copy ascii string to int8_t variable, see CO_fifo_cpyTok2U8 */
|
||||
size_t CO_fifo_cpyTok2I8(CO_fifo_t* dest, CO_fifo_t* src, uint8_t* status);
|
||||
/** Copy ascii string to int16_t variable, see CO_fifo_cpyTok2U8 */
|
||||
size_t CO_fifo_cpyTok2I16(CO_fifo_t* dest, CO_fifo_t* src, uint8_t* status);
|
||||
/** Copy ascii string to int32_t variable, see CO_fifo_cpyTok2U8 */
|
||||
size_t CO_fifo_cpyTok2I32(CO_fifo_t* dest, CO_fifo_t* src, uint8_t* status);
|
||||
/** Copy ascii string to int64_t variable, see CO_fifo_cpyTok2U8 */
|
||||
size_t CO_fifo_cpyTok2I64(CO_fifo_t* dest, CO_fifo_t* src, uint8_t* status);
|
||||
/** Copy ascii string to float32_t variable, see CO_fifo_cpyTok2U8 */
|
||||
size_t CO_fifo_cpyTok2R32(CO_fifo_t* dest, CO_fifo_t* src, uint8_t* status);
|
||||
/** Copy ascii string to float64_t variable, see CO_fifo_cpyTok2U8 */
|
||||
size_t CO_fifo_cpyTok2R64(CO_fifo_t* dest, CO_fifo_t* src, uint8_t* status);
|
||||
/** Copy bytes written as two hex digits into to data. Bytes may be space separated. See CO_fifo_cpyTok2U8 for
|
||||
* parameters. */
|
||||
size_t CO_fifo_cpyTok2Hex(CO_fifo_t* dest, CO_fifo_t* src, uint8_t* status);
|
||||
/** Copy visible string to data. A visible string must be enclosed with double quotes, if it contains space. If a double
|
||||
* quote is used within the string, the quotes are escaped by a second quotes. Input string can not contain newline
|
||||
* characters. See CO_fifo_cpyTok2U8 */
|
||||
size_t CO_fifo_cpyTok2Vs(CO_fifo_t* dest, CO_fifo_t* src, uint8_t* status);
|
||||
/** Read ascii mime-base64 encoded string from src fifo and copy as binary data to dest fifo. Encoding is as specified
|
||||
* in RFC 2045, without CR-LF, but one long string in single line. See also CO_fifo_readU82a */
|
||||
size_t CO_fifo_cpyTok2B64(CO_fifo_t* dest, CO_fifo_t* src, uint8_t* status);
|
||||
|
||||
#endif /* (CO_CONFIG_FIFO) & CO_CONFIG_FIFO_ASCII_DATATYPES */
|
||||
|
||||
/** @} */ /* CO_CANopen_301_fifo */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_FIFO) & CO_CONFIG_FIFO_ENABLE */
|
||||
|
||||
#endif /* CO_FIFO_H */
|
||||
83
Middleware/CANopenNode/301/crc16-ccitt.c
Normal file
83
Middleware/CANopenNode/301/crc16-ccitt.c
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Calculation of CRC 16 CCITT polynomial, x^16 + x^12 + x^5 + 1.
|
||||
*
|
||||
* @file crc16-ccitt.c
|
||||
* @ingroup crc16-ccitt
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2012 - 2020 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 "301/crc16-ccitt.h"
|
||||
|
||||
#if ((CO_CONFIG_CRC16)&CO_CONFIG_CRC16_ENABLE) != 0
|
||||
#if ((CO_CONFIG_CRC16)&CO_CONFIG_CRC16_EXTERNAL) == 0
|
||||
|
||||
/*
|
||||
* CRC table calculated by the following algorithm:
|
||||
*
|
||||
* void crc16_ccitt_table_init(void){
|
||||
* uint16_t i, j;
|
||||
* for(i=0; i<256; i++){
|
||||
* uint16_t crc = 0;
|
||||
* uint16_t c = i << 8;
|
||||
* for(j=0; j<8; j++){
|
||||
* if((crc ^ c) & 0x8000) crc = (crc << 1) ^ 0x1021;
|
||||
* else crc = crc << 1;
|
||||
* c = c << 1;
|
||||
* }
|
||||
* crc16_ccitt_table[i] = crc;
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
static const uint16_t crc16_ccitt_table[256] = {
|
||||
0x0000U, 0x1021U, 0x2042U, 0x3063U, 0x4084U, 0x50A5U, 0x60C6U, 0x70E7U, 0x8108U, 0x9129U, 0xA14AU, 0xB16BU, 0xC18CU,
|
||||
0xD1ADU, 0xE1CEU, 0xF1EFU, 0x1231U, 0x0210U, 0x3273U, 0x2252U, 0x52B5U, 0x4294U, 0x72F7U, 0x62D6U, 0x9339U, 0x8318U,
|
||||
0xB37BU, 0xA35AU, 0xD3BDU, 0xC39CU, 0xF3FFU, 0xE3DEU, 0x2462U, 0x3443U, 0x0420U, 0x1401U, 0x64E6U, 0x74C7U, 0x44A4U,
|
||||
0x5485U, 0xA56AU, 0xB54BU, 0x8528U, 0x9509U, 0xE5EEU, 0xF5CFU, 0xC5ACU, 0xD58DU, 0x3653U, 0x2672U, 0x1611U, 0x0630U,
|
||||
0x76D7U, 0x66F6U, 0x5695U, 0x46B4U, 0xB75BU, 0xA77AU, 0x9719U, 0x8738U, 0xF7DFU, 0xE7FEU, 0xD79DU, 0xC7BCU, 0x48C4U,
|
||||
0x58E5U, 0x6886U, 0x78A7U, 0x0840U, 0x1861U, 0x2802U, 0x3823U, 0xC9CCU, 0xD9EDU, 0xE98EU, 0xF9AFU, 0x8948U, 0x9969U,
|
||||
0xA90AU, 0xB92BU, 0x5AF5U, 0x4AD4U, 0x7AB7U, 0x6A96U, 0x1A71U, 0x0A50U, 0x3A33U, 0x2A12U, 0xDBFDU, 0xCBDCU, 0xFBBFU,
|
||||
0xEB9EU, 0x9B79U, 0x8B58U, 0xBB3BU, 0xAB1AU, 0x6CA6U, 0x7C87U, 0x4CE4U, 0x5CC5U, 0x2C22U, 0x3C03U, 0x0C60U, 0x1C41U,
|
||||
0xEDAEU, 0xFD8FU, 0xCDECU, 0xDDCDU, 0xAD2AU, 0xBD0BU, 0x8D68U, 0x9D49U, 0x7E97U, 0x6EB6U, 0x5ED5U, 0x4EF4U, 0x3E13U,
|
||||
0x2E32U, 0x1E51U, 0x0E70U, 0xFF9FU, 0xEFBEU, 0xDFDDU, 0xCFFCU, 0xBF1BU, 0xAF3AU, 0x9F59U, 0x8F78U, 0x9188U, 0x81A9U,
|
||||
0xB1CAU, 0xA1EBU, 0xD10CU, 0xC12DU, 0xF14EU, 0xE16FU, 0x1080U, 0x00A1U, 0x30C2U, 0x20E3U, 0x5004U, 0x4025U, 0x7046U,
|
||||
0x6067U, 0x83B9U, 0x9398U, 0xA3FBU, 0xB3DAU, 0xC33DU, 0xD31CU, 0xE37FU, 0xF35EU, 0x02B1U, 0x1290U, 0x22F3U, 0x32D2U,
|
||||
0x4235U, 0x5214U, 0x6277U, 0x7256U, 0xB5EAU, 0xA5CBU, 0x95A8U, 0x8589U, 0xF56EU, 0xE54FU, 0xD52CU, 0xC50DU, 0x34E2U,
|
||||
0x24C3U, 0x14A0U, 0x0481U, 0x7466U, 0x6447U, 0x5424U, 0x4405U, 0xA7DBU, 0xB7FAU, 0x8799U, 0x97B8U, 0xE75FU, 0xF77EU,
|
||||
0xC71DU, 0xD73CU, 0x26D3U, 0x36F2U, 0x0691U, 0x16B0U, 0x6657U, 0x7676U, 0x4615U, 0x5634U, 0xD94CU, 0xC96DU, 0xF90EU,
|
||||
0xE92FU, 0x99C8U, 0x89E9U, 0xB98AU, 0xA9ABU, 0x5844U, 0x4865U, 0x7806U, 0x6827U, 0x18C0U, 0x08E1U, 0x3882U, 0x28A3U,
|
||||
0xCB7DU, 0xDB5CU, 0xEB3FU, 0xFB1EU, 0x8BF9U, 0x9BD8U, 0xABBBU, 0xBB9AU, 0x4A75U, 0x5A54U, 0x6A37U, 0x7A16U, 0x0AF1U,
|
||||
0x1AD0U, 0x2AB3U, 0x3A92U, 0xFD2EU, 0xED0FU, 0xDD6CU, 0xCD4DU, 0xBDAAU, 0xAD8BU, 0x9DE8U, 0x8DC9U, 0x7C26U, 0x6C07U,
|
||||
0x5C64U, 0x4C45U, 0x3CA2U, 0x2C83U, 0x1CE0U, 0x0CC1U, 0xEF1FU, 0xFF3EU, 0xCF5DU, 0xDF7CU, 0xAF9BU, 0xBFBAU, 0x8FD9U,
|
||||
0x9FF8U, 0x6E17U, 0x7E36U, 0x4E55U, 0x5E74U, 0x2E93U, 0x3EB2U, 0x0ED1U, 0x1EF0U};
|
||||
|
||||
void
|
||||
crc16_ccitt_single(uint16_t* crc, const uint8_t chr) {
|
||||
uint8_t tmp = (uint8_t)(*crc >> 8U) ^ chr;
|
||||
*crc = (uint16_t)((*crc << 8U) ^ crc16_ccitt_table[tmp]);
|
||||
}
|
||||
|
||||
uint16_t
|
||||
crc16_ccitt(const uint8_t block[], size_t blockLength, uint16_t crc) {
|
||||
size_t i;
|
||||
|
||||
for (i = 0U; i < blockLength; i++) {
|
||||
uint8_t tmp = (uint8_t)((uint8_t)(crc >> 8U) ^ block[i]);
|
||||
crc = (uint16_t)((crc << 8U) ^ crc16_ccitt_table[tmp]);
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
#endif /* !((CO_CONFIG_CRC16) & CO_CONFIG_CRC16_EXTERNAL) */
|
||||
#endif /* (CO_CONFIG_CRC16) & CO_CONFIG_CRC16_ENABLE */
|
||||
80
Middleware/CANopenNode/301/crc16-ccitt.h
Normal file
80
Middleware/CANopenNode/301/crc16-ccitt.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Calculation of CRC 16 CCITT polynomial.
|
||||
*
|
||||
* @file crc16-ccitt.h
|
||||
* @ingroup CO_crc16_ccitt
|
||||
* @author Lammert Bies
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2012 - 2020 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.
|
||||
*/
|
||||
|
||||
#ifndef CRC16_CCITT_H
|
||||
#define CRC16_CCITT_H
|
||||
|
||||
#include "301/CO_driver.h"
|
||||
|
||||
/* default configuration, see CO_config.h */
|
||||
#ifndef CO_CONFIG_CRC16
|
||||
#define CO_CONFIG_CRC16 (0)
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_CRC16)&CO_CONFIG_CRC16_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_crc16_ccitt CRC 16 CCITT
|
||||
* Calculation of CRC 16 CCITT polynomial.
|
||||
*
|
||||
* @ingroup CO_CANopen_301
|
||||
* @{
|
||||
* Equation:
|
||||
*
|
||||
* `x^16 + x^12 + x^5 + 1`
|
||||
*/
|
||||
|
||||
/**
|
||||
* Update crc16_ccitt variable with one data byte
|
||||
*
|
||||
* This function updates crc variable for one data byte using crc16 ccitt algorithm.
|
||||
*
|
||||
* @param [in,out] crc Externally defined variable for CRC checksum. Before start of new CRC calculation, variable must
|
||||
* be initialized (zero for xmodem).
|
||||
* @param chr One byte of data
|
||||
*/
|
||||
void crc16_ccitt_single(uint16_t* crc, const uint8_t chr);
|
||||
|
||||
/**
|
||||
* Calculate CRC sum on block of data.
|
||||
*
|
||||
* @param block Pointer to block of data.
|
||||
* @param blockLength Length of data in bytes;
|
||||
* @param crc Initial value (zero for xmodem). If block is split into multiple segments, previous CRC is used as
|
||||
* initial.
|
||||
*
|
||||
* @return Calculated CRC.
|
||||
*/
|
||||
uint16_t crc16_ccitt(const uint8_t block[], size_t blockLength, uint16_t crc);
|
||||
|
||||
/** @} */ /* CO_crc16_ccitt */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_CRC16) & CO_CONFIG_CRC16_ENABLE */
|
||||
|
||||
#endif /* CRC16_CCITT_H */
|
||||
Reference in New Issue
Block a user