第一版代码,为了在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 */
|
||||
177
Middleware/CANopenNode/303/CO_LEDs.c
Normal file
177
Middleware/CANopenNode/303/CO_LEDs.c
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* CANopen Indicator specification (CiA 303-3 v1.4.0)
|
||||
*
|
||||
* @file CO_LEDs.h
|
||||
* @ingroup CO_LEDs
|
||||
* @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 "303/CO_LEDs.h"
|
||||
|
||||
#if ((CO_CONFIG_LEDS)&CO_CONFIG_LEDS_ENABLE) != 0
|
||||
|
||||
CO_ReturnError_t
|
||||
CO_LEDs_init(CO_LEDs_t* LEDs) {
|
||||
CO_ReturnError_t ret = CO_ERROR_NO;
|
||||
|
||||
/* verify arguments */
|
||||
if (LEDs == NULL) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* clear the object */
|
||||
(void)memset(LEDs, 0, sizeof(CO_LEDs_t));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
CO_LEDs_process(CO_LEDs_t* LEDs, uint32_t timeDifference_us, CO_NMT_internalState_t NMTstate, bool_t LSSconfig,
|
||||
bool_t ErrCANbusOff, bool_t ErrCANbusWarn, bool_t ErrRpdo, bool_t ErrSync, bool_t ErrHbCons,
|
||||
bool_t ErrOther, bool_t firmwareDownload, uint32_t* timerNext_us) {
|
||||
(void)timerNext_us; /* may be unused */
|
||||
|
||||
uint8_t rd = 0;
|
||||
uint8_t gr = 0;
|
||||
bool_t tick = false;
|
||||
|
||||
LEDs->LEDtmr50ms += timeDifference_us;
|
||||
while (LEDs->LEDtmr50ms >= 50000U) {
|
||||
bool_t rdFlickerNext = (LEDs->LEDred & (uint8_t)CO_LED_flicker) == 0U;
|
||||
|
||||
tick = true;
|
||||
LEDs->LEDtmr50ms -= 50000U;
|
||||
|
||||
if (++LEDs->LEDtmr200ms > 3U) {
|
||||
/* calculate 2,5Hz blinking and flashing */
|
||||
LEDs->LEDtmr200ms = 0;
|
||||
rd = 0;
|
||||
gr = 0;
|
||||
|
||||
if ((LEDs->LEDred & CO_LED_blink) == 0U) {
|
||||
rd |= CO_LED_blink;
|
||||
} else {
|
||||
gr |= CO_LED_blink;
|
||||
}
|
||||
|
||||
switch (++LEDs->LEDtmrflash_1) {
|
||||
case 1: rd |= CO_LED_flash_1; break;
|
||||
case 2: gr |= CO_LED_flash_1; break;
|
||||
case 6: LEDs->LEDtmrflash_1 = 0; break;
|
||||
default: /* none */ break;
|
||||
}
|
||||
switch (++LEDs->LEDtmrflash_2) {
|
||||
case 1:
|
||||
case 3: rd |= CO_LED_flash_2; break;
|
||||
case 2:
|
||||
case 4: gr |= CO_LED_flash_2; break;
|
||||
case 8: LEDs->LEDtmrflash_2 = 0; break;
|
||||
default: /* none */ break;
|
||||
}
|
||||
switch (++LEDs->LEDtmrflash_3) {
|
||||
case 1:
|
||||
case 3:
|
||||
case 5: rd |= CO_LED_flash_3; break;
|
||||
case 2:
|
||||
case 4:
|
||||
case 6: gr |= CO_LED_flash_3; break;
|
||||
case 10: LEDs->LEDtmrflash_3 = 0; break;
|
||||
default: /* none */ break;
|
||||
}
|
||||
switch (++LEDs->LEDtmrflash_4) {
|
||||
case 1:
|
||||
case 3:
|
||||
case 5:
|
||||
case 7: rd |= CO_LED_flash_4; break;
|
||||
case 2:
|
||||
case 4:
|
||||
case 6:
|
||||
case 8: gr |= CO_LED_flash_4; break;
|
||||
case 12: LEDs->LEDtmrflash_4 = 0; break;
|
||||
default: /* none */ break;
|
||||
}
|
||||
} else {
|
||||
/* clear flicker and CANopen bits, keep others */
|
||||
rd = LEDs->LEDred & (0xFFU ^ (CO_LED_flicker | CO_LED_CANopen));
|
||||
gr = LEDs->LEDgreen & (0xFFU ^ (CO_LED_flicker | CO_LED_CANopen));
|
||||
}
|
||||
|
||||
/* calculate 10Hz flickering */
|
||||
if (rdFlickerNext) {
|
||||
rd |= CO_LED_flicker;
|
||||
} else {
|
||||
gr |= CO_LED_flicker;
|
||||
}
|
||||
|
||||
} /* while (LEDs->LEDtmr50ms >= 50000) */
|
||||
|
||||
if (tick) {
|
||||
uint8_t rd_co, gr_co;
|
||||
|
||||
/* CANopen red ERROR LED */
|
||||
if (ErrCANbusOff) {
|
||||
rd_co = 1;
|
||||
} else if (NMTstate == CO_NMT_INITIALIZING) {
|
||||
rd_co = rd & CO_LED_flicker;
|
||||
} else if (ErrRpdo) {
|
||||
rd_co = rd & CO_LED_flash_4;
|
||||
} else if (ErrSync) {
|
||||
rd_co = rd & CO_LED_flash_3;
|
||||
} else if (ErrHbCons) {
|
||||
rd_co = rd & CO_LED_flash_2;
|
||||
} else if (ErrCANbusWarn) {
|
||||
rd_co = rd & CO_LED_flash_1;
|
||||
} else if (ErrOther) {
|
||||
rd_co = rd & CO_LED_blink;
|
||||
} else {
|
||||
rd_co = 0;
|
||||
}
|
||||
|
||||
/* CANopen green RUN LED */
|
||||
if (LSSconfig) {
|
||||
gr_co = gr & CO_LED_flicker;
|
||||
} else if (firmwareDownload) {
|
||||
gr_co = gr & CO_LED_flash_3;
|
||||
} else if (NMTstate == CO_NMT_STOPPED) {
|
||||
gr_co = gr & CO_LED_flash_1;
|
||||
} else if (NMTstate == CO_NMT_PRE_OPERATIONAL) {
|
||||
gr_co = gr & CO_LED_blink;
|
||||
} else if (NMTstate == CO_NMT_OPERATIONAL) {
|
||||
gr_co = 1;
|
||||
} else {
|
||||
gr_co = 0;
|
||||
}
|
||||
|
||||
if (rd_co != 0U) {
|
||||
rd |= CO_LED_CANopen;
|
||||
}
|
||||
if (gr_co != 0U) {
|
||||
gr |= CO_LED_CANopen;
|
||||
}
|
||||
LEDs->LEDred = rd;
|
||||
LEDs->LEDgreen = gr;
|
||||
} /* if (tick) */
|
||||
|
||||
#if ((CO_CONFIG_LEDS)&CO_CONFIG_FLAG_TIMERNEXT) != 0
|
||||
if (timerNext_us != NULL) {
|
||||
uint32_t diff = 50000 - LEDs->LEDtmr50ms;
|
||||
if (*timerNext_us > diff) {
|
||||
*timerNext_us = diff;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* (CO_CONFIG_LEDS) & CO_CONFIG_LEDS_ENABLE */
|
||||
142
Middleware/CANopenNode/303/CO_LEDs.h
Normal file
142
Middleware/CANopenNode/303/CO_LEDs.h
Normal file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* CANopen Indicator specification (CiA 303-3 v1.4.0)
|
||||
*
|
||||
* @file CO_LEDs.h
|
||||
* @ingroup CO_LEDs
|
||||
* @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_LEDS_H
|
||||
#define CO_LEDS_H
|
||||
|
||||
#include "301/CO_driver.h"
|
||||
#include "301/CO_NMT_Heartbeat.h"
|
||||
|
||||
/* default configuration, see CO_config.h */
|
||||
#ifndef CO_CONFIG_LEDS
|
||||
#define CO_CONFIG_LEDS (CO_CONFIG_LEDS_ENABLE | CO_CONFIG_GLOBAL_FLAG_TIMERNEXT)
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_LEDS)&CO_CONFIG_LEDS_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_LEDs LED indicators
|
||||
* Specified in standard CiA 303-3.
|
||||
*
|
||||
* @ingroup CO_CANopen_303
|
||||
* @{
|
||||
* CIA 303-3 standard specifies indicator LED diodes, which reflects state of the CANopen device. Green and red leds or
|
||||
* bi-color led can be used.
|
||||
*
|
||||
* CANopen green led - run led:
|
||||
* - flickering: LSS configuration state is active
|
||||
* - blinking: device is in NMT pre-operational state
|
||||
* - single flash: device is in NMT stopped state
|
||||
* - triple flash: a software download is running in the device
|
||||
* - on: device is in NMT operational state
|
||||
*
|
||||
* CANopen red led - error led:
|
||||
* - off: no error
|
||||
* - flickering: LSS node id is not configured, CANopen is not initialized
|
||||
* - blinking: invalid configuration, general error
|
||||
* - single flash: CAN warning limit reached
|
||||
* - double flash: heartbeat consumer - error in remote monitored node
|
||||
* - triple flash: sync message reception timeout
|
||||
* - quadruple flash: PDO has not been received before the event timer elapsed
|
||||
* - on: CAN bus off
|
||||
*
|
||||
* To apply on/off state to the led diode, use #CO_LED_RED or #CO_LED_GREEN macros with one of the @ref CO_LED_bitmasks.
|
||||
* For CANopen leds use #CO_LED_CANopen bitmask.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup CO_LED_bitmasks CO_LED bitmasks
|
||||
* @{
|
||||
* Bitmasks for the LED indicators
|
||||
*/
|
||||
#define CO_LED_flicker 0x01U /**< LED flickering 10Hz */
|
||||
#define CO_LED_blink 0x02U /**< LED blinking 2,5Hz */
|
||||
#define CO_LED_flash_1 0x04U /**< LED single flash */
|
||||
#define CO_LED_flash_2 0x08U /**< LED double flash */
|
||||
#define CO_LED_flash_3 0x10U /**< LED triple flash */
|
||||
#define CO_LED_flash_4 0x20U /**< LED quadruple flash */
|
||||
#define CO_LED_CANopen 0x80U /**< LED CANopen according to CiA 303-3 */
|
||||
/** @} */
|
||||
|
||||
/** Get on/off state for red led for one of the @ref CO_LED_bitmasks */
|
||||
#define CO_LED_RED(LEDs, BITMASK) ((((LEDs)->LEDred & BITMASK) != 0U) ? 1U : 0U)
|
||||
/** Get on/off state for green led for one of the @ref CO_LED_bitmasks */
|
||||
#define CO_LED_GREEN(LEDs, BITMASK) ((((LEDs)->LEDgreen & BITMASK) != 0U) ? 1U : 0U)
|
||||
|
||||
/**
|
||||
* LEDs object, initialized by CO_LEDs_init()
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t LEDtmr50ms; /**< 50ms led timer */
|
||||
uint8_t LEDtmr200ms; /**< 200ms led timer */
|
||||
uint8_t LEDtmrflash_1; /**< single flash led timer */
|
||||
uint8_t LEDtmrflash_2; /**< double flash led timer */
|
||||
uint8_t LEDtmrflash_3; /**< triple flash led timer */
|
||||
uint8_t LEDtmrflash_4; /**< quadruple flash led timer */
|
||||
uint8_t LEDred; /**< red led bitfield, to be combined with @ref CO_LED_bitmasks */
|
||||
uint8_t LEDgreen; /**< green led bitfield, to be combined with @ref CO_LED_bitmasks */
|
||||
} CO_LEDs_t;
|
||||
|
||||
/**
|
||||
* Initialize LEDs object.
|
||||
*
|
||||
* Function must be called in the communication reset section.
|
||||
*
|
||||
* @param LEDs This object will be initialized.
|
||||
*
|
||||
* @return #CO_ReturnError_t CO_ERROR_NO or CO_ERROR_ILLEGAL_ARGUMENT.
|
||||
*/
|
||||
CO_ReturnError_t CO_LEDs_init(CO_LEDs_t* LEDs);
|
||||
|
||||
/**
|
||||
* Process indicator states
|
||||
*
|
||||
* Function must be called cyclically.
|
||||
*
|
||||
* @param LEDs This object.
|
||||
* @param timeDifference_us Time difference from previous function call in [microseconds].
|
||||
* @param NMTstate NMT operating state.
|
||||
* @param LSSconfig Node is in LSS configuration state indication.
|
||||
* @param ErrCANbusOff CAN bus off indication (highest priority).
|
||||
* @param ErrCANbusWarn CAN error warning limit reached indication.
|
||||
* @param ErrRpdo RPDO event timer timeout indication.
|
||||
* @param ErrSync Sync receive timeout indication.
|
||||
* @param ErrHbCons Heartbeat consumer error (remote node) indication.
|
||||
* @param ErrOther Other error indication (lowest priority).
|
||||
* @param firmwareDownload Firmware download is in progress indication.
|
||||
* @param [out] timerNext_us info to OS - see CO_process().
|
||||
*/
|
||||
void CO_LEDs_process(CO_LEDs_t* LEDs, uint32_t timeDifference_us, CO_NMT_internalState_t NMTstate, bool_t LSSconfig,
|
||||
bool_t ErrCANbusOff, bool_t ErrCANbusWarn, bool_t ErrRpdo, bool_t ErrSync, bool_t ErrHbCons,
|
||||
bool_t ErrOther, bool_t firmwareDownload, uint32_t* timerNext_us);
|
||||
|
||||
/** @} */ /* CO_LEDs */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_LEDS) & CO_CONFIG_LEDS_ENABLE */
|
||||
|
||||
#endif /* CO_LEDS_H */
|
||||
133
Middleware/CANopenNode/304/CO_GFC.c
Normal file
133
Middleware/CANopenNode/304/CO_GFC.c
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* CANopen Global fail-safe command protocol.
|
||||
*
|
||||
* @file CO_GFC.c
|
||||
* @ingroup CO_GFC
|
||||
* @author Robert Grüning
|
||||
* @copyright 2020 Robert Grüning
|
||||
* @copyright 2024 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 "304/CO_GFC.h"
|
||||
|
||||
#if ((CO_CONFIG_GFC)&CO_CONFIG_GFC_ENABLE) != 0
|
||||
|
||||
/*
|
||||
* Custom function for reading or writing OD object.
|
||||
*
|
||||
* For more information see file CO_ODinterface.h, OD_IO_t.
|
||||
*/
|
||||
static ODR_t
|
||||
OD_write_1300(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_GFC_t* GFC = stream->object;
|
||||
|
||||
uint8_t value = CO_getUint8(buf);
|
||||
if (value > 1U) {
|
||||
return ODR_INVALID_VALUE;
|
||||
}
|
||||
|
||||
GFC->valid = (value == 1U);
|
||||
|
||||
/* write value to the original location in the Object Dictionary */
|
||||
return OD_writeOriginal(stream, buf, count, countWritten);
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_GFC)&CO_CONFIG_GFC_CONSUMER) != 0
|
||||
static void
|
||||
CO_GFC_receive(void* object, void* msg) {
|
||||
CO_GFC_t* GFC;
|
||||
uint8_t DLC = CO_CANrxMsg_readDLC(msg);
|
||||
|
||||
GFC = (CO_GFC_t*)object; /* this is the correct pointer type of the first argument */
|
||||
|
||||
if (GFC->valid && (DLC == 0U)) {
|
||||
|
||||
/* Callback signals Global Failsafe Command */
|
||||
if (GFC->pFunctSignalSafe != NULL) {
|
||||
GFC->pFunctSignalSafe(GFC->functSignalObjectSafe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CO_GFC_initCallbackEnterSafeState(CO_GFC_t* GFC, void* object, void (*pFunctSignalSafe)(void* object)) {
|
||||
if (GFC != NULL) {
|
||||
GFC->functSignalObjectSafe = object;
|
||||
GFC->pFunctSignalSafe = pFunctSignalSafe;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
CO_ReturnError_t
|
||||
CO_GFC_init(CO_GFC_t* GFC, OD_entry_t* OD_1300_gfcParameter, CO_CANmodule_t* GFC_CANdevRx, uint16_t GFC_rxIdx,
|
||||
uint16_t CANidRxGFC, CO_CANmodule_t* GFC_CANdevTx, uint16_t GFC_txIdx, uint16_t CANidTxGFC) {
|
||||
if ((GFC == NULL) || (OD_1300_gfcParameter == NULL) || (GFC_CANdevRx == NULL) || (GFC_CANdevTx == NULL)) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
uint8_t valid = 0;
|
||||
if (OD_get_u8(OD_1300_gfcParameter, 0, &valid, true) != ODR_OK) {
|
||||
return CO_ERROR_OD_PARAMETERS;
|
||||
}
|
||||
GFC->valid = (valid == 1U);
|
||||
|
||||
/* Configure Object dictionary entry at index 0x1300+ */
|
||||
GFC->OD_gfcParam_ext.object = GFC;
|
||||
GFC->OD_gfcParam_ext.read = OD_readOriginal;
|
||||
GFC->OD_gfcParam_ext.write = OD_write_1300;
|
||||
(void)OD_extension_init(OD_1300_gfcParameter, &GFC->OD_gfcParam_ext);
|
||||
|
||||
#if ((CO_CONFIG_GFC)&CO_CONFIG_GFC_PRODUCER) != 0
|
||||
GFC->CANdevTx = GFC_CANdevTx;
|
||||
GFC->CANtxBuff = CO_CANtxBufferInit(GFC->CANdevTx, GFC_txIdx, CANidTxGFC, false, 0, false);
|
||||
|
||||
if (GFC->CANtxBuff == NULL) {
|
||||
return CO_ERROR_TX_UNCONFIGURED;
|
||||
}
|
||||
#else
|
||||
(void)GFC_txIdx; /* unused */
|
||||
(void)CANidTxGFC; /* unused */
|
||||
#endif
|
||||
|
||||
#if ((CO_CONFIG_GFC)&CO_CONFIG_GFC_CONSUMER) != 0
|
||||
GFC->functSignalObjectSafe = NULL;
|
||||
GFC->pFunctSignalSafe = NULL;
|
||||
const CO_ReturnError_t r = CO_CANrxBufferInit(GFC_CANdevRx, GFC_rxIdx, CANidRxGFC, 0x7FF, false, (void*)GFC,
|
||||
CO_GFC_receive);
|
||||
if (r != CO_ERROR_NO) {
|
||||
return r;
|
||||
}
|
||||
#else
|
||||
(void)GFC_rxIdx; /* unused */
|
||||
(void)CANidRxGFC; /* unused */
|
||||
#endif
|
||||
|
||||
return CO_ERROR_NO;
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_GFC)&CO_CONFIG_GFC_PRODUCER) != 0
|
||||
CO_ReturnError_t
|
||||
CO_GFCsend(CO_GFC_t* GFC) {
|
||||
if (GFC->valid) {
|
||||
return CO_CANsend(GFC->CANdevTx, GFC->CANtxBuff);
|
||||
}
|
||||
return CO_ERROR_NO;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* (CO_CONFIG_GFC) & CO_CONFIG_GFC_ENABLE */
|
||||
122
Middleware/CANopenNode/304/CO_GFC.h
Normal file
122
Middleware/CANopenNode/304/CO_GFC.h
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* CANopen Global fail-safe command protocol.
|
||||
*
|
||||
* @file CO_GFC.h
|
||||
* @ingroup CO_GFC
|
||||
* @author Robert Grüning
|
||||
* @copyright 2020 Robert Grüning
|
||||
* @copyright 2024 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_GFC_H
|
||||
#define CO_GFC_H
|
||||
|
||||
#include "301/CO_driver.h"
|
||||
#include "301/CO_ODinterface.h"
|
||||
|
||||
/* default configuration, see CO_config.h */
|
||||
#ifndef CO_CONFIG_GFC
|
||||
#define CO_CONFIG_GFC (0)
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_GFC)&CO_CONFIG_GFC_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_GFC GFC
|
||||
* Global fail-safe command protocol.
|
||||
*
|
||||
* @ingroup CO_CANopen_304
|
||||
* @{
|
||||
* Very simple consumer/producer protocol. A net can have multiple GFC producer and multiple GFC consumer. On a
|
||||
* safety-relevant the producer can send a GFC message (ID 1, DLC 0). The consumer can use this message to start the
|
||||
* transition to a safe state. The GFC is optional for the security protocol and is not monitored (timed).
|
||||
*/
|
||||
|
||||
/**
|
||||
* GFC object.
|
||||
*/
|
||||
typedef struct {
|
||||
bool_t valid; /**< From OD parameter 1300 */
|
||||
OD_extension_t OD_gfcParam_ext; /**< Extension for OD object */
|
||||
#if (((CO_CONFIG_GFC)&CO_CONFIG_GFC_PRODUCER) != 0) || defined CO_DOXYGEN
|
||||
CO_CANmodule_t* CANdevTx; /**< From CO_GFC_init() */
|
||||
CO_CANtx_t* CANtxBuff; /**< CAN transmit buffer inside CANdevTx */
|
||||
#endif
|
||||
#if (((CO_CONFIG_GFC)&CO_CONFIG_GFC_CONSUMER) != 0) || defined CO_DOXYGEN
|
||||
void (*pFunctSignalSafe)(void* object); /**< From CO_GFC_initCallbackEnterSafeState() or NULL */
|
||||
void* functSignalObjectSafe; /**< From CO_GFC_initCallbackEnterSafeState() or NULL */
|
||||
#endif
|
||||
} CO_GFC_t;
|
||||
|
||||
/**
|
||||
* Initialize GFC object.
|
||||
*
|
||||
* Function must be called in the communication reset section.
|
||||
*
|
||||
* @param GFC This object will be initialized.
|
||||
* @param OD_1300_gfcParameter Pointer to _Global fail-safe command parameter_ variable from Object dictionary (index
|
||||
* 0x1300).
|
||||
* @param GFC_CANdevRx CAN device used for SRDO reception.
|
||||
* @param GFC_rxIdx Index of receive buffer in the above CAN device.
|
||||
* @param CANidRxGFC GFC CAN ID for reception
|
||||
* @param GFC_CANdevTx AN device used for SRDO transmission.
|
||||
* @param GFC_txIdx Index of transmit buffer in the above CAN device.
|
||||
* @param CANidTxGFC GFC CAN ID for transmission
|
||||
*
|
||||
* @return #CO_ReturnError_t: CO_ERROR_NO or CO_ERROR_ILLEGAL_ARGUMENT.
|
||||
*/
|
||||
CO_ReturnError_t CO_GFC_init(CO_GFC_t* GFC, OD_entry_t* OD_1300_gfcParameter, CO_CANmodule_t* GFC_CANdevRx,
|
||||
uint16_t GFC_rxIdx, uint16_t CANidRxGFC, CO_CANmodule_t* GFC_CANdevTx, uint16_t GFC_txIdx,
|
||||
uint16_t CANidTxGFC);
|
||||
|
||||
#if (((CO_CONFIG_GFC)&CO_CONFIG_GFC_CONSUMER) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Initialize GFC callback function.
|
||||
*
|
||||
* Function initializes optional callback function, that is called when GFC is received. Callback is called from receive
|
||||
* function (interrupt).
|
||||
*
|
||||
* @param GFC This object.
|
||||
* @param object Pointer to object, which will be passed to pFunctSignalSafe(). Can be NULL
|
||||
* @param pFunctSignalSafe Pointer to the callback function. Not called if NULL.
|
||||
*/
|
||||
void CO_GFC_initCallbackEnterSafeState(CO_GFC_t* GFC, void* object, void (*pFunctSignalSafe)(void* object));
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_GFC)&CO_CONFIG_GFC_PRODUCER) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Send GFC message.
|
||||
*
|
||||
* It should be called by application, for example after a safety-relevant change.
|
||||
*
|
||||
* @param GFC GFC object.
|
||||
*
|
||||
* @return Same as CO_CANsend().
|
||||
*/
|
||||
CO_ReturnError_t CO_GFCsend(CO_GFC_t* GFC);
|
||||
#endif
|
||||
|
||||
/** @} */ /* CO_GFC */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_GFC) & CO_CONFIG_GFC_ENABLE */
|
||||
|
||||
#endif /* CO_GFC_H */
|
||||
1010
Middleware/CANopenNode/304/CO_SRDO.c
Normal file
1010
Middleware/CANopenNode/304/CO_SRDO.c
Normal file
File diff suppressed because it is too large
Load Diff
285
Middleware/CANopenNode/304/CO_SRDO.h
Normal file
285
Middleware/CANopenNode/304/CO_SRDO.h
Normal file
@@ -0,0 +1,285 @@
|
||||
/**
|
||||
* CANopen Safety Related Data Object protocol.
|
||||
*
|
||||
* @file CO_SRDO.h
|
||||
* @ingroup CO_SRDO
|
||||
* @author Robert Grüning
|
||||
* @copyright 2020 Robert Grüning
|
||||
* @copyright 2024 temi54c1l8(at)github
|
||||
* @copyright 2024 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_SRDO_H
|
||||
#define CO_SRDO_H
|
||||
|
||||
#include "301/CO_Emergency.h"
|
||||
#include "301/CO_ODinterface.h"
|
||||
|
||||
/* default configuration, see CO_config.h */
|
||||
#ifndef CO_CONFIG_SRDO
|
||||
#define CO_CONFIG_SRDO (0)
|
||||
#endif
|
||||
#ifndef CO_CONFIG_SRDO_MINIMUM_DELAY
|
||||
#define CO_CONFIG_SRDO_MINIMUM_DELAY 0U
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_SRDO)&CO_CONFIG_SRDO_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_SRDO SRDO
|
||||
* CANopen Safety Related Data Object protocol
|
||||
*
|
||||
* @ingroup CO_CANopen_304
|
||||
* @{
|
||||
* Safety Related Data Object protocol is specified by standard EN 50325-5:2010 (formerly CiA304). Its functionality is
|
||||
* very similar to that of the PDOs. The main differences is every message is send and received twice. The second
|
||||
* message must be bitwise inverted. The delay between the two messages and between each message pair is monitored. The
|
||||
* distinction between sending and receiving SRDO is made at runtime (for PDO it is compile time). If the security
|
||||
* protocol is used, at least one SRDO is mandatory.
|
||||
*
|
||||
* If there is erroneous structure of OD entries for SRDO parameters, then CO_SRDO_init() function returns error and
|
||||
* CANopen device doesn't work. It is necessary to repair Object Dictionary and reprogram the device.
|
||||
*
|
||||
* If there are erroneous values inside SRDO parameters, then Emergency message @ref CO_EM_SRDO_CONFIGURATION is sent.
|
||||
* Info code (32bit) contains OD index, subindex and additional byte, which helps to determine erroneous OD object.
|
||||
*
|
||||
* SRDO is first configured in CANopen in CANopen initialization section after all other CANopen objects are
|
||||
* initialized. It consists of one CO_SRDOGuard_init() and CO_SRDO_init() for each SRDO. On transition to NMT
|
||||
* operational CO_SRDO_config() must be called for each SRDO.
|
||||
*
|
||||
* CO_SRDO_process() must be executed cyclically, similar as PDO processing. Function is fast, no time consuming tasks.
|
||||
* Function returns @ref CO_SRDO_state_t value, which may be used to determine working-state or safe-state of safety
|
||||
* related device. If return values from all SRDO objects are >= @ref CO_SRDO_state_communicationEstablished, then
|
||||
* working state is allowed. Otherwise SR device must be in safe state.
|
||||
*
|
||||
* Requirement for mapped objects:
|
||||
* - @ref OD_attributes_t must have set bit ODA_RSRDO or ODA_RSRDO or ODA_TRSRDO (by CANopenEditor).
|
||||
*/
|
||||
|
||||
/** Maximum size of SRDO message, 8 for standard CAN */
|
||||
#ifndef CO_SRDO_MAX_SIZE
|
||||
#define CO_SRDO_MAX_SIZE 8U
|
||||
#endif
|
||||
|
||||
/** Maximum number of entries, which can be mapped to SRDO, 2*8 for standard CAN, may be less to preserve RAM usage.
|
||||
* Must be multiple of 2. */
|
||||
#ifndef CO_SRDO_MAX_MAPPED_ENTRIES
|
||||
#define CO_SRDO_MAX_MAPPED_ENTRIES 16U
|
||||
#endif
|
||||
|
||||
#ifndef CO_SRDO_OWN_TYPES
|
||||
/** Variable of type @ref CO_SRDO_size_t contains data length in bytes of SRDO */
|
||||
typedef uint8_t CO_SRDO_size_t;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* SRDO internal state
|
||||
*/
|
||||
typedef enum {
|
||||
CO_SRDO_state_error_internal = -10, /**< internal software error */
|
||||
CO_SRDO_state_error_configuration = -9, /**< error in parameters, emergency message was sent */
|
||||
CO_SRDO_state_error_txNotInverted = -6, /**< Transmitting SRDO messages was not inverted */
|
||||
CO_SRDO_state_error_txFail = -5, /**< SRDO CAN message transmission failed */
|
||||
CO_SRDO_state_error_rxTimeoutSRVT = -4, /**< SRDO message didn't receive inside SRVT time */
|
||||
CO_SRDO_state_error_rxTimeoutSCT = -3, /**< SRDO inverted message didn't receive inside SCT time */
|
||||
CO_SRDO_state_error_rxNotInverted = -2, /**< Received SRDO messages was not inverted */
|
||||
CO_SRDO_state_error_rxShort = -1, /**< Received SRDO message is too short */
|
||||
CO_SRDO_state_unknown = 0, /**< unknown state, set by CO_SRDO_init() */
|
||||
CO_SRDO_state_nmtNotOperational = 1, /**< Internal NMT operating state is not NMT operational */
|
||||
CO_SRDO_state_initializing = 2, /**< Just entered NMT operational state, SRDO message not yet received or
|
||||
transmitted */
|
||||
CO_SRDO_state_communicationEstablished = 3, /**< SRDO communication established, fully functional */
|
||||
CO_SRDO_state_deleted = 10 /**< informationDirection for this SRDO is set to 0 */
|
||||
} CO_SRDO_state_t;
|
||||
|
||||
/**
|
||||
* Guard Object for SRDO.
|
||||
*
|
||||
* Guard object monitors all SRDO objects for:
|
||||
* - access to CRC objects
|
||||
* - access configuration valid flag
|
||||
* - change in operation state
|
||||
*/
|
||||
typedef struct {
|
||||
bool_t NMTisOperational; /**< True if NMT operating state is operational */
|
||||
bool_t configurationValid; /**< True if all SRDO objects are properly configured. Set after successful finish of all
|
||||
CO_SRDO_init() functions. Cleared on configuration change. */
|
||||
OD_IO_t OD_IO_configurationValid; /**< Object for input / output on the OD variable 13FE:00. Configuration of any of
|
||||
the the SRDO parameters will write 0 to that variable. */
|
||||
OD_entry_t* OD_13FE_entry; /**< From CO_SRDOGuard_init() */
|
||||
OD_entry_t* OD_13FF_entry; /**< From CO_SRDOGuard_init() */
|
||||
OD_extension_t OD_13FE_extension; /**< Extension for OD object */
|
||||
OD_extension_t OD_13FF_extension; /**< Extension for OD object */
|
||||
} CO_SRDOGuard_t;
|
||||
|
||||
/**
|
||||
* SRDO object.
|
||||
*/
|
||||
typedef struct {
|
||||
CO_SRDOGuard_t* SRDOGuard; /**< From CO_SRDO_init() */
|
||||
OD_t* OD; /**< From CO_SRDO_init() */
|
||||
CO_EM_t* em; /**< From CO_SRDO_init() */
|
||||
uint16_t defaultCOB_ID; /**< From CO_SRDO_init() */
|
||||
uint8_t nodeId; /**< From CO_SRDO_init() */
|
||||
CO_CANmodule_t* CANdevTx[2]; /**< From CO_SRDO_init() */
|
||||
uint16_t CANdevTxIdx[2]; /**< From CO_SRDO_init() */
|
||||
CO_CANmodule_t* CANdevRx[2]; /**< From CO_SRDO_init() */
|
||||
uint16_t CANdevRxIdx[2]; /**< From CO_SRDO_init() */
|
||||
CO_SRDO_state_t internalState; /**< Internal state of this SRDO. */
|
||||
bool_t NMTisOperationalPrevious; /**< Copy of variable, internal usage. */
|
||||
uint8_t informationDirection; /**< 0 - SRDO is disabled; 1 - SRDO is producer (tx); 2 - SRDO is consumer (rx) */
|
||||
uint32_t cycleTime_us; /**< Safety Cycle Time from object dictionary translated to microseconds */
|
||||
uint32_t validationTime_us; /**< Safety related validation time from object dictionary translated to microseconds */
|
||||
uint32_t cycleTimer; /**< cycle timer variable in microseconds */
|
||||
uint32_t invertedDelay; /**< inverted delay timer variable in microseconds */
|
||||
uint32_t validationTimer; /**< validation timer variable in microseconds */
|
||||
CO_SRDO_size_t dataLength; /**< Data length of the received SRDO message. Calculated from mapping */
|
||||
uint8_t mappedObjectsCount; /**< Number of mapped objects in SRDO */
|
||||
OD_IO_t OD_IO[CO_SRDO_MAX_MAPPED_ENTRIES]; /**< Object dictionary interface for all mapped entries. OD_IO.dataOffset
|
||||
has special usage with SRDO. It stores information about mappedLength
|
||||
of the variable. mappedLength can be less or equal to the
|
||||
OD_IO.dataLength. mappedLength greater than OD_IO.dataLength 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. */
|
||||
CO_CANtx_t* CANtxBuff[2]; /**< CAN transmit buffers inside CANdevTx */
|
||||
volatile void* CANrxNew[2]; /**< Variable indicates, if new SRDO message received from CAN bus. */
|
||||
bool_t rxSrdoShort; /**< true, if received SRDO is too short */
|
||||
uint8_t CANrxData[2][CO_SRDO_MAX_SIZE]; /**< two buffers of data bytes for the received message. */
|
||||
bool_t nextIsNormal; /**< If true, next processed SRDO message is normal (not inverted) */
|
||||
OD_entry_t* OD_communicationParam_entry; /**< From CO_SRDO_init() */
|
||||
OD_entry_t* OD_mappingParam_entry; /**< From CO_SRDO_init() */
|
||||
OD_extension_t OD_communicationParam_ext; /**< Extension for OD object */
|
||||
OD_extension_t OD_mappingParam_extension; /**< Extension for OD object */
|
||||
#if (((CO_CONFIG_SRDO)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
void (*pFunctSignalPre)(void* object); /**< From CO_SRDO_initCallbackPre() or NULL */
|
||||
void* functSignalObjectPre; /**< From CO_SRDO_initCallbackPre() or NULL */
|
||||
#endif
|
||||
} CO_SRDO_t;
|
||||
|
||||
/**
|
||||
* Initialize SRDOGuard object.
|
||||
*
|
||||
* Function must be called in the communication reset section before CO_SRDO_init() functions.
|
||||
*
|
||||
* @param SRDOGuard This object will be initialized.
|
||||
* @param OD_13FE_configurationValid Pointer to _Configuration valid_ variable from Object dictionary (index 0x13FE).
|
||||
* @param OD_13FF_safetyConfigurationSignature Pointer to _Safety configuration signature_ variable from Object
|
||||
* dictionary (index 0x13FF).
|
||||
* @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_SRDOGuard_init(CO_SRDOGuard_t* SRDOGuard, OD_entry_t* OD_13FE_configurationValid,
|
||||
OD_entry_t* OD_13FF_safetyConfigurationSignature, uint32_t* errInfo);
|
||||
|
||||
/**
|
||||
* Initialize SRDO object.
|
||||
*
|
||||
* Function must be called in the communication reset section.
|
||||
*
|
||||
* @param SRDO This object will be initialized.
|
||||
* @param SRDO_Index OD index of this SRDO, 0 for the first.
|
||||
* @param SRDOGuard SRDOGuard object.
|
||||
* @param OD CANopen Object Dictionary
|
||||
* @param em Emergency object.
|
||||
* @param nodeId CANopen Node ID of this device. If default COB_ID is used, value will be added.
|
||||
* @param defaultCOB_ID Default COB ID for this SRDO for plain data (without NodeId).
|
||||
* @param OD_130x_SRDOCommPar Pointer to _SRDO communication parameter_ record from Object dictionary (index 0x1301+).
|
||||
* @param OD_138x_SRDOMapPar Pointer to _SRDO mapping parameter_ record from Object dictionary (index 0x1381+).
|
||||
* @param CANdevRxNormal CAN device used for SRDO reception for normal object.
|
||||
* @param CANdevRxInverted CAN device used for SRDO reception for inverted object.
|
||||
* @param CANdevRxIdxNormal Index of receive buffer in the above CAN device (normal).
|
||||
* @param CANdevRxIdxInverted Index of receive buffer in the above CAN device (inverted).
|
||||
* @param CANdevTxNormal CAN device used for SRDO transmission for normal object.
|
||||
* @param CANdevTxInverted CAN device used for SRDO transmission for inverted object.
|
||||
* @param CANdevTxIdxNormal Index of transmit buffer in the above CAN device (normal).
|
||||
* @param CANdevTxIdxInverted Index of transmit buffer in the above CAN device (inverted).
|
||||
* @param [out] errInfo Additional information in case of error, may be NULL.
|
||||
*
|
||||
* @return #CO_ReturnError_t: CO_ERROR_NO, CO_ERROR_ILLEGAL_ARGUMENT or CO_ERROR_OD_PARAMETERS.
|
||||
*/
|
||||
CO_ReturnError_t CO_SRDO_init(CO_SRDO_t* SRDO, uint8_t SRDO_Index, CO_SRDOGuard_t* SRDOGuard, OD_t* OD, CO_EM_t* em,
|
||||
uint8_t nodeId, uint16_t defaultCOB_ID, OD_entry_t* OD_130x_SRDOCommPar,
|
||||
OD_entry_t* OD_138x_SRDOMapPar, CO_CANmodule_t* CANdevRxNormal,
|
||||
CO_CANmodule_t* CANdevRxInverted, uint16_t CANdevRxIdxNormal,
|
||||
uint16_t CANdevRxIdxInverted, CO_CANmodule_t* CANdevTxNormal,
|
||||
CO_CANmodule_t* CANdevTxInverted, uint16_t CANdevTxIdxNormal,
|
||||
uint16_t CANdevTxIdxInverted, uint32_t* errInfo);
|
||||
|
||||
#if (((CO_CONFIG_SRDO)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Initialize SRDO callback function.
|
||||
*
|
||||
* Function initializes optional callback function, which should immediately start processing of CO_SRDO_process()
|
||||
* function. Callback is called after SRDO message is received from the CAN bus.
|
||||
*
|
||||
* @param SRDO 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_SRDO_initCallbackPre(CO_SRDO_t* SRDO, void* object, void (*pFunctSignalPre)(void* object));
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Configure SRDO object.
|
||||
*
|
||||
* Function must be called in on transition to NMT operational. Function is also called from CO_SRDO_init() function.
|
||||
*
|
||||
* @param SRDO This object will be configured.
|
||||
* @param SRDO_Index OD index of this SRDO, 0 for the first.
|
||||
* @param SRDOGuard SRDOGuard object.
|
||||
* @param [out] errInfo Additional information in case of error, may be NULL.
|
||||
*
|
||||
* @return #CO_ReturnError_t: CO_ERROR_NO, CO_ERROR_ILLEGAL_ARGUMENT or CO_ERROR_OD_PARAMETERS.
|
||||
*/
|
||||
CO_ReturnError_t CO_SRDO_config(CO_SRDO_t* SRDO, uint8_t SRDO_Index, CO_SRDOGuard_t* SRDOGuard, uint32_t* errInfo);
|
||||
|
||||
/**
|
||||
* Send SRDO on event
|
||||
*
|
||||
* Sends SRDO before the next refresh timer tiggers. The message itself is send in CO_SRDO_process(). Note that RTOS
|
||||
* have to trigger its processing quickly. After the transmission the timer is reset to the full refresh time.
|
||||
*
|
||||
* @param SRDO This object.
|
||||
* @return #CO_ReturnError_t: CO_ERROR_NO if request is granted
|
||||
*/
|
||||
CO_ReturnError_t CO_SRDO_requestSend(CO_SRDO_t* SRDO);
|
||||
|
||||
/**
|
||||
* Process transmitting/receiving individual SRDO message.
|
||||
*
|
||||
* @param SRDO This object.
|
||||
* @param timeDifference_us Time difference from previous function call in [microseconds].
|
||||
* @param [out] timerNext_us info to OS, may be null.
|
||||
* @param NMTisOperational True if this node is in NMT_OPERATIONAL state.
|
||||
*
|
||||
* @return CO_SRDO_state_t internal state
|
||||
*/
|
||||
CO_SRDO_state_t CO_SRDO_process(CO_SRDO_t* SRDO, uint32_t timeDifference_us, uint32_t* timerNext_us,
|
||||
bool_t NMTisOperational);
|
||||
|
||||
/** @} */ /* CO_SRDO */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_SRDO) & CO_CONFIG_SRDO_ENABLE */
|
||||
|
||||
#endif /* CO_SRDO_H */
|
||||
223
Middleware/CANopenNode/305/CO_LSS.h
Normal file
223
Middleware/CANopenNode/305/CO_LSS.h
Normal file
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* CANopen Layer Setting Services protocol (common).
|
||||
*
|
||||
* @file CO_LSS.h
|
||||
* @ingroup CO_LSS
|
||||
* @author Martin Wagner
|
||||
* @copyright 2017 - 2020 Neuberger Gebaeudeautomation GmbH
|
||||
*
|
||||
*
|
||||
* 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_LSS_H
|
||||
#define CO_LSS_H
|
||||
|
||||
#include "301/CO_driver.h"
|
||||
|
||||
/* default configuration, see CO_config.h */
|
||||
#ifndef CO_CONFIG_LSS
|
||||
#define CO_CONFIG_LSS (CO_CONFIG_LSS_SLAVE | CO_CONFIG_GLOBAL_FLAG_CALLBACK_PRE)
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_LSS) & (CO_CONFIG_LSS_SLAVE | CO_CONFIG_LSS_MASTER)) != 0) || defined CO_DOXYGEN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_LSS LSS
|
||||
* CANopen Layer Setting Services protocol (common).
|
||||
*
|
||||
* @ingroup CO_CANopen_305
|
||||
* @{
|
||||
* LSS protocol is according to CiA DSP 305 V3.0.0.
|
||||
*
|
||||
* LSS services and protocols are used to inquire or to change the settings of three parameters of the physical layer,
|
||||
* data link layer, and application layer on a CANopen device with LSS slave capability by a CANopen device with LSS
|
||||
* master capability via the CAN network.
|
||||
*
|
||||
* The following parameters may be inquired or changed:
|
||||
* - Node-ID of the CANopen device
|
||||
* - Bit timing parameters of the physical layer (bit rate)
|
||||
* - LSS address compliant to the identity object (1018h)
|
||||
*
|
||||
* The connection is established in one of two ways:
|
||||
* - addressing a node by it's 128 bit LSS address. This requires that the master already knows the node's LSS address.
|
||||
* - scanning the network for unknown nodes (Fastscan). Using this method, unknown devices can be found and configured
|
||||
* one by one.
|
||||
*
|
||||
* Be aware that changing the bit rate is a critical step for the network. A failure will render the network unusable!
|
||||
*
|
||||
* Using this implementation, only master or slave can be included in one node at a time.
|
||||
*
|
||||
* For CAN identifiers see @ref CO_Default_CAN_ID_t
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup CO_LSS_command_specifiers CO_LSS command specifiers
|
||||
* @{
|
||||
*
|
||||
* The LSS protocols are executed between the LSS master device and the LSS slave device(s) to implement the LSS
|
||||
* services. Some LSS protocols require a sequence of CAN messages.
|
||||
*
|
||||
* As identifying method only "LSS fastscan" is supported.
|
||||
*/
|
||||
#define CO_LSS_SWITCH_STATE_GLOBAL 0x04U /**< Switch state global protocol */
|
||||
#define CO_LSS_SWITCH_STATE_SEL_VENDOR 0x40U /**< Switch state selective protocol - Vendor ID */
|
||||
#define CO_LSS_SWITCH_STATE_SEL_PRODUCT 0x41U /**< Switch state selective protocol - Product code */
|
||||
#define CO_LSS_SWITCH_STATE_SEL_REV 0x42U /**< Switch state selective protocol - Revision number */
|
||||
#define CO_LSS_SWITCH_STATE_SEL_SERIAL 0x43U /**< Switch state selective protocol - Serial number */
|
||||
#define CO_LSS_SWITCH_STATE_SEL 0x44U /**< Switch state selective protocol - Slave response */
|
||||
#define CO_LSS_CFG_NODE_ID 0x11U /**< Configure node ID protocol */
|
||||
#define CO_LSS_CFG_BIT_TIMING 0x13U /**< Configure bit timing parameter protocol */
|
||||
#define CO_LSS_CFG_ACTIVATE_BIT_TIMING 0x15U /**< Activate bit timing parameter protocol */
|
||||
#define CO_LSS_CFG_STORE 0x17U /**< Store configuration protocol */
|
||||
#define CO_LSS_IDENT_SLAVE 0x4FU /**< LSS Fastscan response */
|
||||
#define CO_LSS_IDENT_FASTSCAN 0x51U /**< LSS Fastscan protocol */
|
||||
#define CO_LSS_INQUIRE_VENDOR 0x5AU /**< Inquire identity vendor-ID protocol */
|
||||
#define CO_LSS_INQUIRE_PRODUCT 0x5BU /**< Inquire identity product-code protocol */
|
||||
#define CO_LSS_INQUIRE_REV 0x5CU /**< Inquire identity revision-number protocol */
|
||||
#define CO_LSS_INQUIRE_SERIAL 0x5DU /**< Inquire identity serial-number protocol */
|
||||
#define CO_LSS_INQUIRE_NODE_ID 0x5EU /**< Inquire node-ID protocol */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup CO_LSS_CFG_NODE_ID_status CO_LSS_CFG_NODE_ID status
|
||||
* @{
|
||||
* Error codes for Configure node ID protocol
|
||||
*/
|
||||
#define CO_LSS_CFG_NODE_ID_OK 0x00U /**< Protocol successfully completed */
|
||||
#define CO_LSS_CFG_NODE_ID_OUT_OF_RANGE 0x01U /**< NID out of range */
|
||||
#define CO_LSS_CFG_NODE_ID_MANUFACTURER 0xFFU /**< Manufacturer specific error. No further support */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup CO_LSS_CFG_BIT_TIMING_status CO_LSS_CFG_BIT_TIMING status
|
||||
* @{
|
||||
* Error codes for Configure bit timing parameters protocol
|
||||
*/
|
||||
#define CO_LSS_CFG_BIT_TIMING_OK 0x00U /**< Protocol successfully completed */
|
||||
#define CO_LSS_CFG_BIT_TIMING_OUT_OF_RANGE 0x01U /**< Bit timing / Bit rate not supported */
|
||||
#define CO_LSS_CFG_BIT_TIMING_MANUFACTURER 0xFFU /**< Manufacturer specific error. No further support */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup CO_LSS_CFG_STORE_status CO_LSS_CFG_STORE status
|
||||
* @{
|
||||
* Error codes for Store configuration protocol
|
||||
*/
|
||||
#define CO_LSS_CFG_STORE_OK 0x00U /**< Protocol successfully completed */
|
||||
#define CO_LSS_CFG_STORE_NOT_SUPPORTED 0x01U /**< Store configuration not supported */
|
||||
#define CO_LSS_CFG_STORE_FAILED 0x02U /**< Storage media access error */
|
||||
#define CO_LSS_CFG_STORE_MANUFACTURER 0xFFU /**< Manufacturer specific error. No further support */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup CO_LSS_FASTSCAN_bitcheck CO_LSS_FASTSCAN bitcheck
|
||||
* @{
|
||||
* Fastscan BitCheck. BIT0 means all bits are checked for equality by slave
|
||||
*/
|
||||
#define CO_LSS_FASTSCAN_BIT0 0x00U /**< Least significant bit of IDnumbners bit area to be checked */
|
||||
/* ... */
|
||||
#define CO_LSS_FASTSCAN_BIT31 0x1FU /**< dito */
|
||||
#define CO_LSS_FASTSCAN_CONFIRM 0x80U /**< All LSS slaves waiting for scan respond and previous scan is reset */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup CO_LSS_FASTSCAN_lssSub_lssNext CO_LSS_FASTSCAN lssSub lssNext
|
||||
* @{
|
||||
*/
|
||||
#define CO_LSS_FASTSCAN_VENDOR_ID 0x00U /**< Vendor ID */
|
||||
#define CO_LSS_FASTSCAN_PRODUCT 0x01U /**< Product code */
|
||||
#define CO_LSS_FASTSCAN_REV 0x02U /**< Revision number */
|
||||
#define CO_LSS_FASTSCAN_SERIAL 0x03U /**< Serial number */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* The LSS address is a 128 bit number, uniquely identifying each node. It consists of the values in object 0x1018.
|
||||
*/
|
||||
typedef union {
|
||||
uint32_t addr[4];
|
||||
|
||||
struct {
|
||||
uint32_t vendorID;
|
||||
uint32_t productCode;
|
||||
uint32_t revisionNumber;
|
||||
uint32_t serialNumber;
|
||||
} identity;
|
||||
} CO_LSS_address_t;
|
||||
|
||||
/**
|
||||
* @defgroup CO_LSS_STATE_state CO_LSS_STATE state
|
||||
* @{
|
||||
*
|
||||
* The LSS FSA shall provide the following states:
|
||||
* - Initial: Pseudo state, indicating the activation of the FSA.
|
||||
* - LSS waiting: In this state, the LSS slave device waits for requests.
|
||||
* - LSS configuration: In this state variables may be configured in the LSS slave.
|
||||
* - Final: Pseudo state, indicating the deactivation of the FSA.
|
||||
*/
|
||||
#define CO_LSS_STATE_WAITING 0x00U /**< LSS FSA waiting for requests */
|
||||
#define CO_LSS_STATE_CONFIGURATION 0x01U /**< LSS FSA waiting for configuration */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup CO_LSS_BIT_TIMING_table CO_LSS_BIT_TIMING table
|
||||
* @{
|
||||
* Definition of table_index for /CiA301/ bit timing table
|
||||
*/
|
||||
#define CO_LSS_BIT_TIMING_1000 0U /**< 1000kbit/s */
|
||||
#define CO_LSS_BIT_TIMING_800 1U /**< 800kbit/s */
|
||||
#define CO_LSS_BIT_TIMING_500 2U /**< 500kbit/s */
|
||||
#define CO_LSS_BIT_TIMING_250 3U /**< 250kbit/s */
|
||||
#define CO_LSS_BIT_TIMING_125 4U /**< 125kbit/s */
|
||||
/* 5U - reserved */
|
||||
#define CO_LSS_BIT_TIMING_50 6U /**< 50kbit/s */
|
||||
#define CO_LSS_BIT_TIMING_20 7U /**< 20kbit/s */
|
||||
#define CO_LSS_BIT_TIMING_10 8U /**< 10kbit/s */
|
||||
#define CO_LSS_BIT_TIMING_AUTO 9U /**< Automatic bit rate detection */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Lookup table for conversion between bit timing table and numerical bit rate
|
||||
*/
|
||||
static const uint16_t CO_LSS_bitTimingTableLookup[] = {1000, 800, 500, 250, 125, 0, 50, 20, 10, 0};
|
||||
|
||||
/**
|
||||
* Invalid node ID triggers node ID assignment
|
||||
*/
|
||||
#define CO_LSS_NODE_ID_ASSIGNMENT 0xFFU
|
||||
|
||||
/**
|
||||
* Macro to check if node id is valid
|
||||
*/
|
||||
#define CO_LSS_NODE_ID_VALID(nid) (((nid >= 1U) && (nid <= 0x7FU)) || (nid == CO_LSS_NODE_ID_ASSIGNMENT))
|
||||
|
||||
/**
|
||||
* Macro to check if two LSS addresses are equal
|
||||
*/
|
||||
#define CO_LSS_ADDRESS_EQUAL(/* CO_LSS_address_t */ a1, /* CO_LSS_address_t */ a2) \
|
||||
((a1.identity.productCode == a2.identity.productCode) \
|
||||
&& (a1.identity.revisionNumber == a2.identity.revisionNumber) \
|
||||
&& (a1.identity.serialNumber == a2.identity.serialNumber) && (a1.identity.vendorID == a2.identity.vendorID))
|
||||
|
||||
/** @} */ /* @defgroup CO_LSS */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_LSS) & (CO_CONFIG_LSS_SLAVE | CO_CONFIG_LSS_MASTER) */
|
||||
|
||||
#endif /* CO_LSS_H */
|
||||
965
Middleware/CANopenNode/305/CO_LSSmaster.c
Normal file
965
Middleware/CANopenNode/305/CO_LSSmaster.c
Normal file
@@ -0,0 +1,965 @@
|
||||
/*
|
||||
* CANopen LSS Master protocol.
|
||||
*
|
||||
* @file CO_LSSmaster.c
|
||||
* @ingroup CO_LSS
|
||||
* @author Martin Wagner
|
||||
* @copyright 2017 - 2020 Neuberger Gebaeudeautomation GmbH
|
||||
*
|
||||
*
|
||||
* 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 "305/CO_LSSmaster.h"
|
||||
|
||||
#if ((CO_CONFIG_LSS)&CO_CONFIG_LSS_MASTER) != 0
|
||||
|
||||
/*
|
||||
* @defgroup CO_LSSmaster_state_t
|
||||
* @{
|
||||
* LSS master slave select state machine. Compared to @ref CO_LSS_STATE_state this has information if we
|
||||
* currently have selected one or all slaves. This allows for some basic error checking.
|
||||
*/
|
||||
#define CO_LSSmaster_STATE_WAITING 0x00U
|
||||
#define CO_LSSmaster_STATE_CFG_SLECTIVE 0x01U
|
||||
#define CO_LSSmaster_STATE_CFG_GLOBAL 0x02U
|
||||
/* @} */ /* CO_LSSmaster_state_t */
|
||||
|
||||
/*
|
||||
* @defgroup CO_LSSmaster_command_t LSS master slave command state machine
|
||||
* @{
|
||||
*/
|
||||
#define CO_LSSmaster_COMMAND_WAITING 0x00U
|
||||
#define CO_LSSmaster_COMMAND_SWITCH_STATE 0x01U
|
||||
#define CO_LSSmaster_COMMAND_CFG_BIT_TIMING 0x02U
|
||||
#define CO_LSSmaster_COMMAND_CFG_NODE_ID 0x03U
|
||||
#define CO_LSSmaster_COMMAND_CFG_STORE 0x04U
|
||||
#define CO_LSSmaster_COMMAND_INQUIRE_VENDOR 0x05U
|
||||
#define CO_LSSmaster_COMMAND_INQUIRE_PRODUCT 0x06U
|
||||
#define CO_LSSmaster_COMMAND_INQUIRE_REV 0x07U
|
||||
#define CO_LSSmaster_COMMAND_INQUIRE_SERIAL 0x08U
|
||||
#define CO_LSSmaster_COMMAND_INQUIRE 0x09U
|
||||
#define CO_LSSmaster_COMMAND_IDENTIFY_FASTSCAN 0x0AU
|
||||
/* @} */ /* CO_LSSmaster_command_t */
|
||||
|
||||
/*
|
||||
* @defgroup CO_LSSmaster_fs_t LSS master fastscan state machine
|
||||
* @{
|
||||
*/
|
||||
#define CO_LSSmaster_FS_STATE_CHECK 0x00U
|
||||
#define CO_LSSmaster_FS_STATE_SCAN 0x01U
|
||||
#define CO_LSSmaster_FS_STATE_VERIFY 0x02U
|
||||
|
||||
/* @} */ /* CO_LSSmaster_fs_t */
|
||||
|
||||
/*
|
||||
* Read received message from CAN module.
|
||||
*
|
||||
* Function will be called (by CAN receive interrupt) every time, when CAN message with correct identifier
|
||||
* will be received. For more information and description of parameters see file CO_driver.h.
|
||||
*/
|
||||
static void
|
||||
CO_LSSmaster_receive(void* object, void* msg) {
|
||||
CO_LSSmaster_t* LSSmaster;
|
||||
uint8_t DLC = CO_CANrxMsg_readDLC(msg);
|
||||
const uint8_t* data = CO_CANrxMsg_readData(msg);
|
||||
|
||||
LSSmaster = (CO_LSSmaster_t*)object; /* this is the correct pointer type of the first argument */
|
||||
|
||||
/* verify message length and message overflow (previous message was not processed yet). */
|
||||
if ((DLC == 8U) && !CO_FLAG_READ(LSSmaster->CANrxNew) && (LSSmaster->command != CO_LSSmaster_COMMAND_WAITING)) {
|
||||
|
||||
/* copy data and set 'new message' flag */
|
||||
(void)memcpy(LSSmaster->CANrxData, data, sizeof(LSSmaster->CANrxData));
|
||||
|
||||
CO_FLAG_SET(LSSmaster->CANrxNew);
|
||||
|
||||
#if ((CO_CONFIG_LSS)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0
|
||||
/* Optional signal to RTOS, which can resume task, which handles further processing. */
|
||||
if (LSSmaster->pFunctSignal != NULL) {
|
||||
LSSmaster->pFunctSignal(LSSmaster->functSignalObject);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check LSS timeout.
|
||||
*
|
||||
* Generally, we do not really care if the message has been received before or after the timeout
|
||||
* expired. Only if no message has been received we have to check for timeouts.
|
||||
*/
|
||||
static inline CO_LSSmaster_return_t
|
||||
CO_LSSmaster_check_timeout(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us) {
|
||||
CO_LSSmaster_return_t ret = CO_LSSmaster_WAIT_SLAVE;
|
||||
|
||||
LSSmaster->timeoutTimer += timeDifference_us;
|
||||
if (LSSmaster->timeoutTimer >= LSSmaster->timeout_us) {
|
||||
LSSmaster->timeoutTimer = 0;
|
||||
ret = CO_LSSmaster_TIMEOUT;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
CO_ReturnError_t
|
||||
CO_LSSmaster_init(CO_LSSmaster_t* LSSmaster, uint16_t timeout_ms, CO_CANmodule_t* CANdevRx, uint16_t CANdevRxIdx,
|
||||
uint16_t CANidLssSlave, CO_CANmodule_t* CANdevTx, uint16_t CANdevTxIdx, uint16_t CANidLssMaster) {
|
||||
CO_ReturnError_t ret = CO_ERROR_NO;
|
||||
|
||||
/* verify arguments */
|
||||
if ((LSSmaster == NULL) || (CANdevRx == NULL) || (CANdevTx == NULL)) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
LSSmaster->timeout_us = (uint32_t)timeout_ms * 1000U;
|
||||
LSSmaster->state = CO_LSSmaster_STATE_WAITING;
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_WAITING;
|
||||
LSSmaster->timeoutTimer = 0;
|
||||
CO_FLAG_CLEAR(LSSmaster->CANrxNew);
|
||||
(void)memset(LSSmaster->CANrxData, 0, sizeof(LSSmaster->CANrxData));
|
||||
#if ((CO_CONFIG_LSS)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0
|
||||
LSSmaster->pFunctSignal = NULL;
|
||||
LSSmaster->functSignalObject = NULL;
|
||||
#endif
|
||||
|
||||
/* configure LSS CAN Slave response message reception */
|
||||
ret = CO_CANrxBufferInit(CANdevRx, CANdevRxIdx, CANidLssSlave, 0x7FF, false, (void*)LSSmaster,
|
||||
CO_LSSmaster_receive);
|
||||
|
||||
/* configure LSS CAN Master message transmission */
|
||||
LSSmaster->CANdevTx = CANdevTx;
|
||||
LSSmaster->TXbuff = CO_CANtxBufferInit(CANdevTx, CANdevTxIdx, CANidLssMaster, false, 8, false);
|
||||
|
||||
if (LSSmaster->TXbuff == NULL) {
|
||||
ret = CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
CO_LSSmaster_changeTimeout(CO_LSSmaster_t* LSSmaster, uint16_t timeout_ms) {
|
||||
if (LSSmaster != NULL) {
|
||||
LSSmaster->timeout_us = (uint32_t)timeout_ms * 1000U;
|
||||
}
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_LSS)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0
|
||||
void
|
||||
CO_LSSmaster_initCallbackPre(CO_LSSmaster_t* LSSmaster, void* object, void (*pFunctSignal)(void* object)) {
|
||||
if (LSSmaster != NULL) {
|
||||
LSSmaster->functSignalObject = object;
|
||||
LSSmaster->pFunctSignal = pFunctSignal;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Helper function - initiate switch state
|
||||
*/
|
||||
static CO_LSSmaster_return_t
|
||||
CO_LSSmaster_switchStateSelectInitiate(CO_LSSmaster_t* LSSmaster, CO_LSS_address_t* lssAddress) {
|
||||
CO_LSSmaster_return_t ret;
|
||||
|
||||
if (lssAddress != NULL) {
|
||||
/* switch state select specific using LSS address */
|
||||
LSSmaster->state = CO_LSSmaster_STATE_CFG_SLECTIVE;
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_SWITCH_STATE;
|
||||
LSSmaster->timeoutTimer = 0;
|
||||
|
||||
CO_FLAG_CLEAR(LSSmaster->CANrxNew);
|
||||
(void)memset(&LSSmaster->TXbuff->data[6], 0, sizeof(LSSmaster->TXbuff->data) - 6U);
|
||||
LSSmaster->TXbuff->data[0] = CO_LSS_SWITCH_STATE_SEL_VENDOR;
|
||||
(void)CO_setUint32(&LSSmaster->TXbuff->data[1], CO_SWAP_32(lssAddress->identity.vendorID));
|
||||
(void)CO_CANsend(LSSmaster->CANdevTx, LSSmaster->TXbuff);
|
||||
LSSmaster->TXbuff->data[0] = CO_LSS_SWITCH_STATE_SEL_PRODUCT;
|
||||
(void)CO_setUint32(&LSSmaster->TXbuff->data[1], CO_SWAP_32(lssAddress->identity.productCode));
|
||||
(void)CO_CANsend(LSSmaster->CANdevTx, LSSmaster->TXbuff);
|
||||
LSSmaster->TXbuff->data[0] = CO_LSS_SWITCH_STATE_SEL_REV;
|
||||
(void)CO_setUint32(&LSSmaster->TXbuff->data[1], CO_SWAP_32(lssAddress->identity.revisionNumber));
|
||||
(void)CO_CANsend(LSSmaster->CANdevTx, LSSmaster->TXbuff);
|
||||
LSSmaster->TXbuff->data[0] = CO_LSS_SWITCH_STATE_SEL_SERIAL;
|
||||
(void)CO_setUint32(&LSSmaster->TXbuff->data[1], CO_SWAP_32(lssAddress->identity.serialNumber));
|
||||
(void)CO_CANsend(LSSmaster->CANdevTx, LSSmaster->TXbuff);
|
||||
|
||||
ret = CO_LSSmaster_WAIT_SLAVE;
|
||||
} else {
|
||||
/* switch state global */
|
||||
LSSmaster->state = CO_LSSmaster_STATE_CFG_GLOBAL;
|
||||
|
||||
CO_FLAG_CLEAR(LSSmaster->CANrxNew);
|
||||
LSSmaster->TXbuff->data[0] = CO_LSS_SWITCH_STATE_GLOBAL;
|
||||
LSSmaster->TXbuff->data[1] = CO_LSS_STATE_CONFIGURATION;
|
||||
(void)memset(&LSSmaster->TXbuff->data[2], 0, sizeof(LSSmaster->TXbuff->data) - 2U);
|
||||
(void)CO_CANsend(LSSmaster->CANdevTx, LSSmaster->TXbuff);
|
||||
|
||||
/* This is non-confirmed service! */
|
||||
ret = CO_LSSmaster_OK;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function - wait for confirmation
|
||||
*/
|
||||
static CO_LSSmaster_return_t
|
||||
CO_LSSmaster_switchStateSelectWait(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us) {
|
||||
CO_LSSmaster_return_t ret;
|
||||
|
||||
if (CO_FLAG_READ(LSSmaster->CANrxNew)) {
|
||||
uint8_t cs = LSSmaster->CANrxData[0];
|
||||
CO_FLAG_CLEAR(LSSmaster->CANrxNew);
|
||||
|
||||
if (cs == CO_LSS_SWITCH_STATE_SEL) {
|
||||
/* confirmation received */
|
||||
ret = CO_LSSmaster_OK;
|
||||
} else {
|
||||
ret = CO_LSSmaster_check_timeout(LSSmaster, timeDifference_us);
|
||||
}
|
||||
} else {
|
||||
ret = CO_LSSmaster_check_timeout(LSSmaster, timeDifference_us);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
CO_LSSmaster_return_t
|
||||
CO_LSSmaster_swStateSelect(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us, CO_LSS_address_t* lssAddress) {
|
||||
CO_LSSmaster_return_t ret = CO_LSSmaster_INVALID_STATE;
|
||||
|
||||
if (LSSmaster == NULL) {
|
||||
return CO_LSSmaster_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* Initiate select */
|
||||
if ((LSSmaster->state == CO_LSSmaster_STATE_WAITING) && (LSSmaster->command == CO_LSSmaster_COMMAND_WAITING)) {
|
||||
|
||||
ret = CO_LSSmaster_switchStateSelectInitiate(LSSmaster, lssAddress);
|
||||
}
|
||||
/* Wait for confirmation */
|
||||
else if (LSSmaster->command == CO_LSSmaster_COMMAND_SWITCH_STATE) {
|
||||
ret = CO_LSSmaster_switchStateSelectWait(LSSmaster, timeDifference_us);
|
||||
} else { /* MISRA C 2004 14.10 */
|
||||
}
|
||||
|
||||
if ((ret != CO_LSSmaster_INVALID_STATE) && (ret != CO_LSSmaster_WAIT_SLAVE)) {
|
||||
/* finished */
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_WAITING;
|
||||
}
|
||||
if (ret < CO_LSSmaster_OK) {
|
||||
/* switching failed, go back to waiting */
|
||||
LSSmaster->state = CO_LSSmaster_STATE_WAITING;
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_WAITING;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
CO_LSSmaster_return_t
|
||||
CO_LSSmaster_swStateDeselect(CO_LSSmaster_t* LSSmaster) {
|
||||
CO_LSSmaster_return_t ret = CO_LSSmaster_INVALID_STATE;
|
||||
|
||||
if (LSSmaster == NULL) {
|
||||
return CO_LSSmaster_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* We can always send this command to get into a clean state on the network.
|
||||
* If no slave is selected, this command is ignored. */
|
||||
LSSmaster->state = CO_LSSmaster_STATE_WAITING;
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_WAITING;
|
||||
LSSmaster->timeoutTimer = 0;
|
||||
|
||||
/* switch state global */
|
||||
CO_FLAG_CLEAR(LSSmaster->CANrxNew);
|
||||
LSSmaster->TXbuff->data[0] = CO_LSS_SWITCH_STATE_GLOBAL;
|
||||
LSSmaster->TXbuff->data[1] = CO_LSS_STATE_WAITING;
|
||||
(void)memset(&LSSmaster->TXbuff->data[2], 0, sizeof(LSSmaster->TXbuff->data) - 2U);
|
||||
(void)CO_CANsend(LSSmaster->CANdevTx, LSSmaster->TXbuff);
|
||||
|
||||
/* This is non-confirmed service! */
|
||||
ret = CO_LSSmaster_OK;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function - wait for confirmation, check for returned error code
|
||||
*
|
||||
* This uses the nature of the configure confirmation message design:
|
||||
* - byte 0 -> cs
|
||||
* - byte 1 -> Error Code, where
|
||||
* - 0 = OK
|
||||
* - 1 .. FE = Values defined by CiA. All currently defined values are slave rejects.
|
||||
* No further distinction on why the slave did reject the request.
|
||||
* - FF = Manufacturer Error Code in byte 2
|
||||
* - byte 2 -> Manufacturer Error, currently not used
|
||||
*
|
||||
* enums for the errorCode are
|
||||
* - CO_LSS_CFG_NODE_ID_status
|
||||
* - CO_LSS_CFG_BIT_TIMING
|
||||
* - CO_LSS_CFG_STORE_status
|
||||
*/
|
||||
static CO_LSSmaster_return_t
|
||||
CO_LSSmaster_configureCheckWait(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us, uint8_t csWait) {
|
||||
CO_LSSmaster_return_t ret;
|
||||
|
||||
if (CO_FLAG_READ(LSSmaster->CANrxNew)) {
|
||||
uint8_t cs = LSSmaster->CANrxData[0];
|
||||
uint8_t errorCode = LSSmaster->CANrxData[1];
|
||||
CO_FLAG_CLEAR(LSSmaster->CANrxNew);
|
||||
|
||||
if (cs == csWait) {
|
||||
if (errorCode == 0U) {
|
||||
ret = CO_LSSmaster_OK;
|
||||
} else if (errorCode == 0xFFU) {
|
||||
ret = CO_LSSmaster_OK_MANUFACTURER;
|
||||
} else {
|
||||
ret = CO_LSSmaster_OK_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
} else {
|
||||
ret = CO_LSSmaster_check_timeout(LSSmaster, timeDifference_us);
|
||||
}
|
||||
} else {
|
||||
ret = CO_LSSmaster_check_timeout(LSSmaster, timeDifference_us);
|
||||
}
|
||||
|
||||
if ((ret != CO_LSSmaster_INVALID_STATE) && (ret != CO_LSSmaster_WAIT_SLAVE)) {
|
||||
/* finished */
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_WAITING;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
CO_LSSmaster_return_t
|
||||
CO_LSSmaster_configureBitTiming(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us, uint16_t bit) {
|
||||
CO_LSSmaster_return_t ret = CO_LSSmaster_INVALID_STATE;
|
||||
uint8_t bitTiming;
|
||||
|
||||
if (LSSmaster == NULL) {
|
||||
return CO_LSSmaster_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
switch (bit) {
|
||||
case 1000: bitTiming = CO_LSS_BIT_TIMING_1000; break;
|
||||
case 800: bitTiming = CO_LSS_BIT_TIMING_800; break;
|
||||
case 500: bitTiming = CO_LSS_BIT_TIMING_500; break;
|
||||
case 250: bitTiming = CO_LSS_BIT_TIMING_250; break;
|
||||
case 125: bitTiming = CO_LSS_BIT_TIMING_125; break;
|
||||
case 50: bitTiming = CO_LSS_BIT_TIMING_50; break;
|
||||
case 20: bitTiming = CO_LSS_BIT_TIMING_20; break;
|
||||
case 10: bitTiming = CO_LSS_BIT_TIMING_10; break;
|
||||
case 0: bitTiming = CO_LSS_BIT_TIMING_AUTO; break;
|
||||
default: return CO_LSSmaster_ILLEGAL_ARGUMENT; break;
|
||||
}
|
||||
|
||||
/* Initiate config bit */
|
||||
if ((LSSmaster->state == CO_LSSmaster_STATE_CFG_SLECTIVE) && (LSSmaster->command == CO_LSSmaster_COMMAND_WAITING)) {
|
||||
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_CFG_BIT_TIMING;
|
||||
LSSmaster->timeoutTimer = 0;
|
||||
|
||||
CO_FLAG_CLEAR(LSSmaster->CANrxNew);
|
||||
LSSmaster->TXbuff->data[0] = CO_LSS_CFG_BIT_TIMING;
|
||||
LSSmaster->TXbuff->data[1] = 0;
|
||||
LSSmaster->TXbuff->data[2] = bitTiming;
|
||||
(void)memset(&LSSmaster->TXbuff->data[3], 0, sizeof(LSSmaster->TXbuff->data) - 3U);
|
||||
(void)CO_CANsend(LSSmaster->CANdevTx, LSSmaster->TXbuff);
|
||||
|
||||
ret = CO_LSSmaster_WAIT_SLAVE;
|
||||
}
|
||||
/* Wait for confirmation */
|
||||
else if (LSSmaster->command == CO_LSSmaster_COMMAND_CFG_BIT_TIMING) {
|
||||
|
||||
ret = CO_LSSmaster_configureCheckWait(LSSmaster, timeDifference_us, CO_LSS_CFG_BIT_TIMING);
|
||||
} else { /* MISRA C 2004 14.10 */
|
||||
}
|
||||
|
||||
if ((ret != CO_LSSmaster_INVALID_STATE) && (ret != CO_LSSmaster_WAIT_SLAVE)) {
|
||||
/* finished */
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_WAITING;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
CO_LSSmaster_return_t
|
||||
CO_LSSmaster_configureNodeId(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us, uint8_t nodeId) {
|
||||
CO_LSSmaster_return_t ret = CO_LSSmaster_INVALID_STATE;
|
||||
|
||||
if ((LSSmaster == NULL) || !CO_LSS_NODE_ID_VALID(nodeId)) {
|
||||
return CO_LSSmaster_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* Initiate config node ID */
|
||||
if (((LSSmaster->state == CO_LSSmaster_STATE_CFG_SLECTIVE) ||
|
||||
/* Let un-config node ID also be run in global mode for unconfiguring all nodes */
|
||||
((LSSmaster->state == CO_LSSmaster_STATE_CFG_GLOBAL) && (nodeId == CO_LSS_NODE_ID_ASSIGNMENT)))
|
||||
&& (LSSmaster->command == CO_LSSmaster_COMMAND_WAITING)) {
|
||||
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_CFG_NODE_ID;
|
||||
LSSmaster->timeoutTimer = 0;
|
||||
|
||||
CO_FLAG_CLEAR(LSSmaster->CANrxNew);
|
||||
LSSmaster->TXbuff->data[0] = CO_LSS_CFG_NODE_ID;
|
||||
LSSmaster->TXbuff->data[1] = nodeId;
|
||||
(void)memset(&LSSmaster->TXbuff->data[2], 0, sizeof(LSSmaster->TXbuff->data) - 2U);
|
||||
(void)CO_CANsend(LSSmaster->CANdevTx, LSSmaster->TXbuff);
|
||||
|
||||
ret = CO_LSSmaster_WAIT_SLAVE;
|
||||
}
|
||||
/* Wait for confirmation */
|
||||
else if (LSSmaster->command == CO_LSSmaster_COMMAND_CFG_NODE_ID) {
|
||||
|
||||
ret = CO_LSSmaster_configureCheckWait(LSSmaster, timeDifference_us, CO_LSS_CFG_NODE_ID);
|
||||
} else { /* MISRA C 2004 14.10 */
|
||||
}
|
||||
|
||||
if ((ret != CO_LSSmaster_INVALID_STATE) && (ret != CO_LSSmaster_WAIT_SLAVE)) {
|
||||
/* finished */
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_WAITING;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
CO_LSSmaster_return_t
|
||||
CO_LSSmaster_configureStore(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us) {
|
||||
CO_LSSmaster_return_t ret = CO_LSSmaster_INVALID_STATE;
|
||||
|
||||
if (LSSmaster == NULL) {
|
||||
return CO_LSSmaster_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* Initiate config store */
|
||||
if ((LSSmaster->state == CO_LSSmaster_STATE_CFG_SLECTIVE) && (LSSmaster->command == CO_LSSmaster_COMMAND_WAITING)) {
|
||||
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_CFG_STORE;
|
||||
LSSmaster->timeoutTimer = 0;
|
||||
|
||||
CO_FLAG_CLEAR(LSSmaster->CANrxNew);
|
||||
LSSmaster->TXbuff->data[0] = CO_LSS_CFG_STORE;
|
||||
(void)memset(&LSSmaster->TXbuff->data[1], 0, sizeof(LSSmaster->TXbuff->data) - 1U);
|
||||
(void)CO_CANsend(LSSmaster->CANdevTx, LSSmaster->TXbuff);
|
||||
|
||||
ret = CO_LSSmaster_WAIT_SLAVE;
|
||||
}
|
||||
/* Wait for confirmation */
|
||||
else if (LSSmaster->command == CO_LSSmaster_COMMAND_CFG_STORE) {
|
||||
|
||||
ret = CO_LSSmaster_configureCheckWait(LSSmaster, timeDifference_us, CO_LSS_CFG_STORE);
|
||||
} else { /* MISRA C 2004 14.10 */
|
||||
}
|
||||
|
||||
if ((ret != CO_LSSmaster_INVALID_STATE) && (ret != CO_LSSmaster_WAIT_SLAVE)) {
|
||||
/* finished */
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_WAITING;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
CO_LSSmaster_return_t
|
||||
CO_LSSmaster_ActivateBit(CO_LSSmaster_t* LSSmaster, uint16_t switchDelay_ms) {
|
||||
CO_LSSmaster_return_t ret = CO_LSSmaster_INVALID_STATE;
|
||||
|
||||
if (LSSmaster == NULL) {
|
||||
return CO_LSSmaster_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* for activating bit timing, we need to have all slaves set to config
|
||||
* state. This check makes it a bit harder to shoot ourselves in the foot */
|
||||
if ((LSSmaster->state == CO_LSSmaster_STATE_CFG_GLOBAL) && (LSSmaster->command == CO_LSSmaster_COMMAND_WAITING)) {
|
||||
|
||||
CO_FLAG_CLEAR(LSSmaster->CANrxNew);
|
||||
LSSmaster->TXbuff->data[0] = CO_LSS_CFG_ACTIVATE_BIT_TIMING;
|
||||
(void)CO_setUint16(&LSSmaster->TXbuff->data[1], CO_SWAP_16(switchDelay_ms));
|
||||
(void)memset(&LSSmaster->TXbuff->data[3], 0, sizeof(LSSmaster->TXbuff->data) - 3U);
|
||||
(void)CO_CANsend(LSSmaster->CANdevTx, LSSmaster->TXbuff);
|
||||
|
||||
/* This is non-confirmed service! */
|
||||
ret = CO_LSSmaster_OK;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function - send request
|
||||
*/
|
||||
static CO_LSSmaster_return_t
|
||||
CO_LSSmaster_inquireInitiate(CO_LSSmaster_t* LSSmaster, uint8_t cs) {
|
||||
CO_FLAG_CLEAR(LSSmaster->CANrxNew);
|
||||
LSSmaster->TXbuff->data[0] = cs;
|
||||
(void)memset(&LSSmaster->TXbuff->data[1], 0, sizeof(LSSmaster->TXbuff->data) - 1U);
|
||||
(void)CO_CANsend(LSSmaster->CANdevTx, LSSmaster->TXbuff);
|
||||
|
||||
return CO_LSSmaster_WAIT_SLAVE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function - wait for confirmation
|
||||
*/
|
||||
static CO_LSSmaster_return_t
|
||||
CO_LSSmaster_inquireCheckWait(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us, uint8_t csWait, uint32_t* value) {
|
||||
CO_LSSmaster_return_t ret;
|
||||
|
||||
if (CO_FLAG_READ(LSSmaster->CANrxNew)) {
|
||||
uint8_t cs = LSSmaster->CANrxData[0];
|
||||
*value = CO_getUint32(&LSSmaster->CANrxData[1]);
|
||||
*value = CO_SWAP_32(*value);
|
||||
CO_FLAG_CLEAR(LSSmaster->CANrxNew);
|
||||
|
||||
if (cs == csWait) {
|
||||
ret = CO_LSSmaster_OK;
|
||||
} else {
|
||||
ret = CO_LSSmaster_check_timeout(LSSmaster, timeDifference_us);
|
||||
}
|
||||
} else {
|
||||
ret = CO_LSSmaster_check_timeout(LSSmaster, timeDifference_us);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
CO_LSSmaster_return_t
|
||||
CO_LSSmaster_InquireLssAddress(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us, CO_LSS_address_t* lssAddress) {
|
||||
CO_LSSmaster_return_t ret = CO_LSSmaster_INVALID_STATE;
|
||||
uint8_t next = CO_LSSmaster_COMMAND_WAITING;
|
||||
|
||||
if ((LSSmaster == NULL) || (lssAddress == NULL)) {
|
||||
return CO_LSSmaster_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* Check for reply */
|
||||
if (LSSmaster->command == CO_LSSmaster_COMMAND_INQUIRE_VENDOR) {
|
||||
|
||||
ret = CO_LSSmaster_inquireCheckWait(LSSmaster, timeDifference_us, CO_LSS_INQUIRE_VENDOR,
|
||||
&lssAddress->identity.vendorID);
|
||||
if (ret == CO_LSSmaster_OK) {
|
||||
/* Start next request */
|
||||
next = CO_LSSmaster_COMMAND_INQUIRE_PRODUCT;
|
||||
ret = CO_LSSmaster_WAIT_SLAVE;
|
||||
}
|
||||
} else if (LSSmaster->command == CO_LSSmaster_COMMAND_INQUIRE_PRODUCT) {
|
||||
|
||||
ret = CO_LSSmaster_inquireCheckWait(LSSmaster, timeDifference_us, CO_LSS_INQUIRE_PRODUCT,
|
||||
&lssAddress->identity.productCode);
|
||||
if (ret == CO_LSSmaster_OK) {
|
||||
/* Start next request */
|
||||
next = CO_LSSmaster_COMMAND_INQUIRE_REV;
|
||||
ret = CO_LSSmaster_WAIT_SLAVE;
|
||||
}
|
||||
} else if (LSSmaster->command == CO_LSSmaster_COMMAND_INQUIRE_REV) {
|
||||
|
||||
ret = CO_LSSmaster_inquireCheckWait(LSSmaster, timeDifference_us, CO_LSS_INQUIRE_REV,
|
||||
&lssAddress->identity.revisionNumber);
|
||||
if (ret == CO_LSSmaster_OK) {
|
||||
/* Start next request */
|
||||
next = CO_LSSmaster_COMMAND_INQUIRE_SERIAL;
|
||||
ret = CO_LSSmaster_WAIT_SLAVE;
|
||||
}
|
||||
} else if (LSSmaster->command == CO_LSSmaster_COMMAND_INQUIRE_SERIAL) {
|
||||
|
||||
ret = CO_LSSmaster_inquireCheckWait(LSSmaster, timeDifference_us, CO_LSS_INQUIRE_SERIAL,
|
||||
&lssAddress->identity.serialNumber);
|
||||
} else { /* MISRA C 2004 14.10 */
|
||||
}
|
||||
|
||||
/* Check for next request */
|
||||
if ((LSSmaster->state == CO_LSSmaster_STATE_CFG_SLECTIVE) || (LSSmaster->state == CO_LSSmaster_STATE_CFG_GLOBAL)) {
|
||||
if (LSSmaster->command == CO_LSSmaster_COMMAND_WAITING) {
|
||||
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_INQUIRE_VENDOR;
|
||||
LSSmaster->timeoutTimer = 0;
|
||||
|
||||
ret = CO_LSSmaster_inquireInitiate(LSSmaster, CO_LSS_INQUIRE_VENDOR);
|
||||
} else if (next == CO_LSSmaster_COMMAND_INQUIRE_PRODUCT) {
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_INQUIRE_PRODUCT;
|
||||
LSSmaster->timeoutTimer = 0;
|
||||
|
||||
ret = CO_LSSmaster_inquireInitiate(LSSmaster, CO_LSS_INQUIRE_PRODUCT);
|
||||
} else if (next == CO_LSSmaster_COMMAND_INQUIRE_REV) {
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_INQUIRE_REV;
|
||||
LSSmaster->timeoutTimer = 0;
|
||||
|
||||
ret = CO_LSSmaster_inquireInitiate(LSSmaster, CO_LSS_INQUIRE_REV);
|
||||
} else if (next == CO_LSSmaster_COMMAND_INQUIRE_SERIAL) {
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_INQUIRE_SERIAL;
|
||||
LSSmaster->timeoutTimer = 0;
|
||||
|
||||
ret = CO_LSSmaster_inquireInitiate(LSSmaster, CO_LSS_INQUIRE_SERIAL);
|
||||
} else { /* MISRA C 2004 14.10 */
|
||||
}
|
||||
}
|
||||
|
||||
if ((ret != CO_LSSmaster_INVALID_STATE) && (ret != CO_LSSmaster_WAIT_SLAVE)) {
|
||||
/* finished */
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_WAITING;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
CO_LSSmaster_return_t
|
||||
CO_LSSmaster_Inquire(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us, uint8_t lssInquireCs, uint32_t* value) {
|
||||
CO_LSSmaster_return_t ret = CO_LSSmaster_INVALID_STATE;
|
||||
|
||||
if ((LSSmaster == NULL) || (value == NULL)) {
|
||||
return CO_LSSmaster_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* send request */
|
||||
if (((LSSmaster->state == CO_LSSmaster_STATE_CFG_SLECTIVE) || (LSSmaster->state == CO_LSSmaster_STATE_CFG_GLOBAL))
|
||||
&& (LSSmaster->command == CO_LSSmaster_COMMAND_WAITING)) {
|
||||
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_INQUIRE;
|
||||
LSSmaster->timeoutTimer = 0;
|
||||
|
||||
ret = CO_LSSmaster_inquireInitiate(LSSmaster, lssInquireCs);
|
||||
}
|
||||
/* Check for reply */
|
||||
else if (LSSmaster->command == CO_LSSmaster_COMMAND_INQUIRE) {
|
||||
ret = CO_LSSmaster_inquireCheckWait(LSSmaster, timeDifference_us, lssInquireCs, value);
|
||||
} else { /* MISRA C 2004 14.10 */
|
||||
}
|
||||
|
||||
if (ret != CO_LSSmaster_WAIT_SLAVE) {
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_WAITING;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function - send request
|
||||
*/
|
||||
static void
|
||||
CO_LSSmaster_FsSendMsg(CO_LSSmaster_t* LSSmaster, uint32_t idNumber, uint8_t bitCheck, uint8_t lssSub,
|
||||
uint8_t lssNext) {
|
||||
LSSmaster->timeoutTimer = 0;
|
||||
|
||||
CO_FLAG_CLEAR(LSSmaster->CANrxNew);
|
||||
LSSmaster->TXbuff->data[0] = CO_LSS_IDENT_FASTSCAN;
|
||||
(void)CO_setUint32(&LSSmaster->TXbuff->data[1], CO_SWAP_32(idNumber));
|
||||
LSSmaster->TXbuff->data[5] = bitCheck;
|
||||
LSSmaster->TXbuff->data[6] = lssSub;
|
||||
LSSmaster->TXbuff->data[7] = lssNext;
|
||||
|
||||
(void)CO_CANsend(LSSmaster->CANdevTx, LSSmaster->TXbuff);
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function - wait for confirmation
|
||||
*/
|
||||
static CO_LSSmaster_return_t
|
||||
CO_LSSmaster_FsCheckWait(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us) {
|
||||
CO_LSSmaster_return_t ret;
|
||||
|
||||
ret = CO_LSSmaster_check_timeout(LSSmaster, timeDifference_us);
|
||||
if (ret == CO_LSSmaster_TIMEOUT) {
|
||||
ret = CO_LSSmaster_SCAN_NOACK;
|
||||
|
||||
if (CO_FLAG_READ(LSSmaster->CANrxNew)) {
|
||||
uint8_t cs = LSSmaster->CANrxData[0];
|
||||
CO_FLAG_CLEAR(LSSmaster->CANrxNew);
|
||||
|
||||
if (cs == CO_LSS_IDENT_SLAVE) {
|
||||
/* At least one node is waiting for fastscan */
|
||||
ret = CO_LSSmaster_SCAN_FINISHED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function - initiate scan for 32 bit part of LSS address
|
||||
*/
|
||||
static CO_LSSmaster_return_t
|
||||
CO_LSSmaster_FsScanInitiate(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us, CO_LSSmaster_scantype_t scan,
|
||||
uint8_t lssSub) {
|
||||
(void)timeDifference_us; /* unused */
|
||||
|
||||
LSSmaster->fsLssSub = lssSub;
|
||||
LSSmaster->fsIdNumber = 0;
|
||||
|
||||
switch (scan) {
|
||||
case CO_LSSmaster_FS_SCAN: break;
|
||||
case CO_LSSmaster_FS_MATCH:
|
||||
/* No scanning requested */
|
||||
return CO_LSSmaster_SCAN_FINISHED;
|
||||
break;
|
||||
case CO_LSSmaster_FS_SKIP:
|
||||
default: return CO_LSSmaster_SCAN_FAILED; break;
|
||||
}
|
||||
|
||||
LSSmaster->fsBitChecked = CO_LSS_FASTSCAN_BIT31;
|
||||
|
||||
/* trigger scan procedure by sending first message */
|
||||
CO_LSSmaster_FsSendMsg(LSSmaster, LSSmaster->fsIdNumber, LSSmaster->fsBitChecked, LSSmaster->fsLssSub,
|
||||
LSSmaster->fsLssSub);
|
||||
|
||||
return CO_LSSmaster_WAIT_SLAVE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function - scan for 32 bits of LSS address, one by one
|
||||
*/
|
||||
static CO_LSSmaster_return_t
|
||||
CO_LSSmaster_FsScanWait(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us, CO_LSSmaster_scantype_t scan) {
|
||||
CO_LSSmaster_return_t ret;
|
||||
|
||||
switch (scan) {
|
||||
case CO_LSSmaster_FS_SCAN: break;
|
||||
case CO_LSSmaster_FS_MATCH:
|
||||
/* No scanning requested */
|
||||
return CO_LSSmaster_SCAN_FINISHED;
|
||||
break;
|
||||
case CO_LSSmaster_FS_SKIP:
|
||||
default: return CO_LSSmaster_SCAN_FAILED; break;
|
||||
}
|
||||
|
||||
ret = CO_LSSmaster_check_timeout(LSSmaster, timeDifference_us);
|
||||
if (ret == CO_LSSmaster_TIMEOUT) {
|
||||
|
||||
ret = CO_LSSmaster_WAIT_SLAVE;
|
||||
|
||||
if (CO_FLAG_READ(LSSmaster->CANrxNew)) {
|
||||
uint8_t cs = LSSmaster->CANrxData[0];
|
||||
CO_FLAG_CLEAR(LSSmaster->CANrxNew);
|
||||
|
||||
if (cs != CO_LSS_IDENT_SLAVE) {
|
||||
/* wrong response received. Can not continue */
|
||||
return CO_LSSmaster_SCAN_FAILED;
|
||||
}
|
||||
} else {
|
||||
/* no response received, assumption is wrong */
|
||||
LSSmaster->fsIdNumber |= 1UL << LSSmaster->fsBitChecked;
|
||||
}
|
||||
|
||||
if (LSSmaster->fsBitChecked == CO_LSS_FASTSCAN_BIT0) {
|
||||
/* Scanning cycle is finished, we now have 32 bit address data */
|
||||
ret = CO_LSSmaster_SCAN_FINISHED;
|
||||
} else {
|
||||
LSSmaster->fsBitChecked--;
|
||||
|
||||
CO_LSSmaster_FsSendMsg(LSSmaster, LSSmaster->fsIdNumber, LSSmaster->fsBitChecked, LSSmaster->fsLssSub,
|
||||
LSSmaster->fsLssSub);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function - initiate check for 32 bit part of LSS address
|
||||
*/
|
||||
static CO_LSSmaster_return_t
|
||||
CO_LSSmaster_FsVerifyInitiate(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us, CO_LSSmaster_scantype_t scan,
|
||||
uint32_t idNumberCheck, uint8_t lssNext) {
|
||||
(void)timeDifference_us; /* unused */
|
||||
|
||||
switch (scan) {
|
||||
case CO_LSSmaster_FS_SCAN:
|
||||
/* ID obtained by scan */
|
||||
break;
|
||||
case CO_LSSmaster_FS_MATCH:
|
||||
/* ID given by user */
|
||||
LSSmaster->fsIdNumber = idNumberCheck;
|
||||
break;
|
||||
case CO_LSSmaster_FS_SKIP:
|
||||
default: return CO_LSSmaster_SCAN_FAILED; break;
|
||||
}
|
||||
|
||||
LSSmaster->fsBitChecked = CO_LSS_FASTSCAN_BIT0;
|
||||
|
||||
/* send request */
|
||||
CO_LSSmaster_FsSendMsg(LSSmaster, LSSmaster->fsIdNumber, LSSmaster->fsBitChecked, LSSmaster->fsLssSub, lssNext);
|
||||
|
||||
return CO_LSSmaster_WAIT_SLAVE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function - verify 32 bit LSS address, request node(s) to switch their state machine to the next state
|
||||
*/
|
||||
static CO_LSSmaster_return_t
|
||||
CO_LSSmaster_FsVerifyWait(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us, CO_LSSmaster_scantype_t scan,
|
||||
uint32_t* idNumberRet) {
|
||||
CO_LSSmaster_return_t ret;
|
||||
|
||||
if (scan == CO_LSSmaster_FS_SKIP) {
|
||||
return CO_LSSmaster_SCAN_FAILED;
|
||||
}
|
||||
|
||||
ret = CO_LSSmaster_check_timeout(LSSmaster, timeDifference_us);
|
||||
if (ret == CO_LSSmaster_TIMEOUT) {
|
||||
|
||||
*idNumberRet = 0;
|
||||
ret = CO_LSSmaster_SCAN_NOACK;
|
||||
|
||||
if (CO_FLAG_READ(LSSmaster->CANrxNew)) {
|
||||
uint8_t cs = LSSmaster->CANrxData[0];
|
||||
CO_FLAG_CLEAR(LSSmaster->CANrxNew);
|
||||
|
||||
if (cs == CO_LSS_IDENT_SLAVE) {
|
||||
*idNumberRet = LSSmaster->fsIdNumber;
|
||||
ret = CO_LSSmaster_SCAN_FINISHED;
|
||||
} else {
|
||||
ret = CO_LSSmaster_SCAN_FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function - check which 32 bit to scan for next, if any
|
||||
*/
|
||||
static uint8_t
|
||||
CO_LSSmaster_FsSearchNext(CO_LSSmaster_t* LSSmaster, const CO_LSSmaster_fastscan_t* fastscan) {
|
||||
uint8_t i;
|
||||
|
||||
/* we search for the next LSS address part to scan for, beginning with the
|
||||
* one after the current one. If there is none remaining, scanning is finished */
|
||||
for (i = LSSmaster->fsLssSub + 1U; i <= CO_LSS_FASTSCAN_SERIAL; i++) {
|
||||
if (fastscan->scan[i] != CO_LSSmaster_FS_SKIP) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
/* node selection is triggered by switching node state machine back to initial state */
|
||||
return CO_LSS_FASTSCAN_VENDOR_ID;
|
||||
}
|
||||
|
||||
CO_LSSmaster_return_t
|
||||
CO_LSSmaster_IdentifyFastscan(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us,
|
||||
CO_LSSmaster_fastscan_t* fastscan) {
|
||||
uint8_t i;
|
||||
uint8_t count;
|
||||
CO_LSSmaster_return_t ret = CO_LSSmaster_INVALID_STATE;
|
||||
uint8_t next;
|
||||
|
||||
/* parameter validation */
|
||||
if ((LSSmaster == NULL) || (fastscan == NULL)) {
|
||||
return CO_LSSmaster_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
if (fastscan->scan[0] == CO_LSSmaster_FS_SKIP) {
|
||||
/* vendor ID scan cannot be skipped */
|
||||
return CO_LSSmaster_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
count = 0;
|
||||
for (i = 0; i < (sizeof(fastscan->scan) / sizeof(fastscan->scan[0])); i++) {
|
||||
if (fastscan->scan[i] == CO_LSSmaster_FS_SKIP) {
|
||||
count++;
|
||||
}
|
||||
if (count > 2U) {
|
||||
/* Node selection needs the Vendor ID and at least one other value */
|
||||
return CO_LSSmaster_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
}
|
||||
|
||||
/* state machine validation */
|
||||
if ((LSSmaster->state != CO_LSSmaster_STATE_WAITING)
|
||||
|| ((LSSmaster->command != CO_LSSmaster_COMMAND_WAITING)
|
||||
&& (LSSmaster->command != CO_LSSmaster_COMMAND_IDENTIFY_FASTSCAN))) {
|
||||
/* state machine not ready, other command is already processed */
|
||||
return CO_LSSmaster_INVALID_STATE;
|
||||
}
|
||||
|
||||
/* evaluate LSS state machine */
|
||||
if (LSSmaster->command == CO_LSSmaster_COMMAND_WAITING) {
|
||||
/* start fastscan */
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_IDENTIFY_FASTSCAN;
|
||||
|
||||
/* check if any nodes are waiting, if yes fastscan is reset */
|
||||
LSSmaster->fsState = CO_LSSmaster_FS_STATE_CHECK;
|
||||
CO_LSSmaster_FsSendMsg(LSSmaster, 0, CO_LSS_FASTSCAN_CONFIRM, 0, 0);
|
||||
|
||||
return CO_LSSmaster_WAIT_SLAVE;
|
||||
} else {
|
||||
/* continue with evaluating fastscan state machine */
|
||||
}
|
||||
|
||||
/*
|
||||
* evaluate fastscan state machine. The state machine is evaluated as following
|
||||
* - check for non-configured nodes
|
||||
* - scan for vendor ID
|
||||
* - verify vendor ID, switch node state
|
||||
* - scan for product code
|
||||
* - verify product code, switch node state
|
||||
* - scan for revision number
|
||||
* - verify revision number, switch node state
|
||||
* - scan for serial number
|
||||
* - verify serial number, switch node to LSS configuration mode
|
||||
* Certain steps can be skipped as mentioned in the function description. If one step is
|
||||
* not ack'ed by a node, the scanning process is terminated and the correspondign error is returned.
|
||||
*/
|
||||
switch (LSSmaster->fsState) {
|
||||
case CO_LSSmaster_FS_STATE_CHECK:
|
||||
ret = CO_LSSmaster_FsCheckWait(LSSmaster, timeDifference_us);
|
||||
if (ret == CO_LSSmaster_SCAN_FINISHED) {
|
||||
(void)memset(&fastscan->found, 0, sizeof(fastscan->found));
|
||||
|
||||
/* start scanning procedure by triggering vendor ID scan */
|
||||
(void)CO_LSSmaster_FsScanInitiate(LSSmaster, timeDifference_us,
|
||||
fastscan->scan[CO_LSS_FASTSCAN_VENDOR_ID], CO_LSS_FASTSCAN_VENDOR_ID);
|
||||
ret = CO_LSSmaster_WAIT_SLAVE;
|
||||
|
||||
LSSmaster->fsState = CO_LSSmaster_FS_STATE_SCAN;
|
||||
}
|
||||
break;
|
||||
case CO_LSSmaster_FS_STATE_SCAN:
|
||||
ret = CO_LSSmaster_FsScanWait(LSSmaster, timeDifference_us, fastscan->scan[LSSmaster->fsLssSub]);
|
||||
if (ret == CO_LSSmaster_SCAN_FINISHED) {
|
||||
/* scanning finished, initiate verifcation. The verification message also contains
|
||||
* the node state machine "switch to next state" request */
|
||||
next = CO_LSSmaster_FsSearchNext(LSSmaster, fastscan);
|
||||
ret = CO_LSSmaster_FsVerifyInitiate(LSSmaster, timeDifference_us, fastscan->scan[LSSmaster->fsLssSub],
|
||||
fastscan->match.addr[LSSmaster->fsLssSub], next);
|
||||
|
||||
LSSmaster->fsState = CO_LSSmaster_FS_STATE_VERIFY;
|
||||
}
|
||||
break;
|
||||
case CO_LSSmaster_FS_STATE_VERIFY:
|
||||
ret = CO_LSSmaster_FsVerifyWait(LSSmaster, timeDifference_us, fastscan->scan[LSSmaster->fsLssSub],
|
||||
&fastscan->found.addr[LSSmaster->fsLssSub]);
|
||||
if (ret == CO_LSSmaster_SCAN_FINISHED) {
|
||||
/* verification successful:
|
||||
* - assumed node id is correct
|
||||
* - node state machine has switched to the requested state, mirror that in the local copy */
|
||||
next = CO_LSSmaster_FsSearchNext(LSSmaster, fastscan);
|
||||
if (next == CO_LSS_FASTSCAN_VENDOR_ID) {
|
||||
/* fastscan finished, one node is now in LSS configuration mode */
|
||||
LSSmaster->state = CO_LSSmaster_STATE_CFG_SLECTIVE;
|
||||
} else {
|
||||
/* initiate scan for next part of LSS address */
|
||||
ret = CO_LSSmaster_FsScanInitiate(LSSmaster, timeDifference_us, fastscan->scan[next], next);
|
||||
if (ret == CO_LSSmaster_SCAN_FINISHED) {
|
||||
/* Scanning is not requested. Initiate verification step in next function call */
|
||||
ret = CO_LSSmaster_WAIT_SLAVE;
|
||||
}
|
||||
|
||||
LSSmaster->fsState = CO_LSSmaster_FS_STATE_SCAN;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* none */
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret != CO_LSSmaster_WAIT_SLAVE) {
|
||||
/* finished */
|
||||
LSSmaster->command = CO_LSSmaster_COMMAND_WAITING;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif /* (CO_CONFIG_LSS) & CO_CONFIG_LSS_MASTER */
|
||||
389
Middleware/CANopenNode/305/CO_LSSmaster.h
Normal file
389
Middleware/CANopenNode/305/CO_LSSmaster.h
Normal file
@@ -0,0 +1,389 @@
|
||||
/**
|
||||
* CANopen Layer Setting Service - master protocol.
|
||||
*
|
||||
* @file CO_LSSmaster.h
|
||||
* @ingroup CO_LSS
|
||||
* @author Martin Wagner
|
||||
* @copyright 2017 - 2020 Neuberger Gebaeudeautomation GmbH
|
||||
*
|
||||
*
|
||||
* 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_LSSmaster_H
|
||||
#define CO_LSSmaster_H
|
||||
|
||||
#include "305/CO_LSS.h"
|
||||
|
||||
#if (((CO_CONFIG_LSS)&CO_CONFIG_LSS_MASTER) != 0) || defined CO_DOXYGEN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_LSSmaster LSS Master
|
||||
* CANopen Layer Setting Service - master protocol.
|
||||
*
|
||||
* @ingroup CO_CANopen_305
|
||||
* @{
|
||||
* The client/master can use the following services
|
||||
* - node selection via LSS address
|
||||
* - node selection via LSS fastscan
|
||||
* - Inquire LSS address of currently selected node
|
||||
* - Inquire node ID
|
||||
* - Configure bit timing
|
||||
* - Configure node ID
|
||||
* - Activate bit timing parameters
|
||||
* - Store configuration
|
||||
*
|
||||
* The LSS master is initalized during the CANopenNode initialization process. Except for enabling the LSS master in the
|
||||
* configurator, no further run-time configuration is needed for basic operation. The LSS master does basic checking of
|
||||
* commands and command sequence.
|
||||
*
|
||||
* ###Usage
|
||||
* Usage of the CANopen LSS master is demonstrated in file 309/CO_gateway_ascii.c
|
||||
*
|
||||
* It essentially is always as following:
|
||||
* - select node(s)
|
||||
* - call master command(s)
|
||||
* - evaluate return value
|
||||
* - deselect nodes
|
||||
*
|
||||
* All commands need to be run cyclically, e.g. like this
|
||||
* \code{.c}
|
||||
interval = 0;
|
||||
do {
|
||||
ret = CO_LSSmaster_InquireNodeId(LSSmaster, interval, &outval);
|
||||
|
||||
interval = 1;
|
||||
ms sleep(interval);
|
||||
} while (ret == CO_LSSmaster_WAIT_SLAVE);
|
||||
* \endcode
|
||||
*
|
||||
* A more advanced implementation can make use of the callback function to shorten waiting times.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return values of LSS master functions.
|
||||
*/
|
||||
typedef enum {
|
||||
CO_LSSmaster_SCAN_FINISHED = 2, /**< Scanning finished successful */
|
||||
CO_LSSmaster_WAIT_SLAVE = 1, /**< No response arrived from slave yet */
|
||||
CO_LSSmaster_OK = 0, /**< Success, end of communication */
|
||||
CO_LSSmaster_TIMEOUT = -1, /**< No reply received */
|
||||
CO_LSSmaster_ILLEGAL_ARGUMENT = -2, /**< Invalid argument */
|
||||
CO_LSSmaster_INVALID_STATE = -3, /**< State machine not ready or already processing a request */
|
||||
CO_LSSmaster_SCAN_NOACK = -4, /**< No node found that matches scan request */
|
||||
CO_LSSmaster_SCAN_FAILED = -5, /**< An error occurred while scanning. Try again */
|
||||
CO_LSSmaster_OK_ILLEGAL_ARGUMENT = -101, /**< LSS success, node rejected argument because of non-supported value */
|
||||
CO_LSSmaster_OK_MANUFACTURER = -102, /**< LSS success, node rejected argument with manufacturer error code */
|
||||
} CO_LSSmaster_return_t;
|
||||
|
||||
/**
|
||||
* LSS master object.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t timeout_us; /**< LSS response timeout in us */
|
||||
uint8_t state; /**< Node is currently selected */
|
||||
uint8_t command; /**< Active command */
|
||||
uint32_t timeoutTimer; /**< Timeout timer for LSS communication */
|
||||
uint8_t fsState; /**< Current state of fastscan master state machine */
|
||||
uint8_t fsLssSub; /**< Current state of node state machine */
|
||||
uint8_t fsBitChecked; /**< Current scan bit position */
|
||||
uint32_t fsIdNumber; /**< Current scan result */
|
||||
volatile void* CANrxNew; /**< Indication if new LSS message is received from CAN bus. It needs to be cleared when
|
||||
received message is completely processed. */
|
||||
uint8_t CANrxData[8]; /**< 8 data bytes of the received message */
|
||||
#if (((CO_CONFIG_LSS)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
void (*pFunctSignal)(void* object); /**< From CO_LSSmaster_initCallbackPre() or NULL */
|
||||
void* functSignalObject; /**< Pointer to object */
|
||||
#endif
|
||||
CO_CANmodule_t* CANdevTx; /**< From CO_LSSmaster_init() */
|
||||
CO_CANtx_t* TXbuff; /**< CAN transmit buffer */
|
||||
} CO_LSSmaster_t;
|
||||
|
||||
/**
|
||||
* Default timeout for LSS slave in ms. This is the same as for SDO. For more info about LSS timeout see
|
||||
* #CO_LSSmaster_changeTimeout()
|
||||
*/
|
||||
#ifndef CO_LSSmaster_DEFAULT_TIMEOUT
|
||||
#define CO_LSSmaster_DEFAULT_TIMEOUT 1000U /* ms */
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initialize LSS object.
|
||||
*
|
||||
* Function must be called in the communication reset section.
|
||||
*
|
||||
* @param LSSmaster This object will be initialized.
|
||||
* @param timeout_ms slave response timeout in ms, for more detail see #CO_LSSmaster_changeTimeout()
|
||||
* @param CANdevRx CAN device for LSS master reception.
|
||||
* @param CANdevRxIdx Index of receive buffer in the above CAN device.
|
||||
* @param CANidLssSlave COB ID for reception.
|
||||
* @param CANdevTx CAN device for LSS master transmission.
|
||||
* @param CANdevTxIdx Index of transmit buffer in the above CAN device.
|
||||
* @param CANidLssMaster COB ID for transmission.
|
||||
* @return #CO_ReturnError_t: CO_ERROR_NO or CO_ERROR_ILLEGAL_ARGUMENT.
|
||||
*/
|
||||
CO_ReturnError_t CO_LSSmaster_init(CO_LSSmaster_t* LSSmaster, uint16_t timeout_ms, CO_CANmodule_t* CANdevRx,
|
||||
uint16_t CANdevRxIdx, uint16_t CANidLssSlave, CO_CANmodule_t* CANdevTx,
|
||||
uint16_t CANdevTxIdx, uint16_t CANidLssMaster);
|
||||
|
||||
/**
|
||||
* Change LSS master timeout
|
||||
*
|
||||
* On LSS, a "negative ack" is signaled by the slave not answering. Because of that, a low timeout value can
|
||||
* significantly increase protocol speed in some cases (e.g. fastscan). However, as soon as there is activity on the
|
||||
* bus, LSS messages can be delayed because of their low CAN network priority (see @ref CO_Default_CAN_ID_t).
|
||||
*
|
||||
* @remark Be aware that a "late response" will seriously mess up LSS, so this value must be selected "as high as
|
||||
* necessary and as low as possible". CiA does neither specify nor recommend a value.
|
||||
*
|
||||
* @remark This timeout is per-transfer. If a command internally needs multiple transfers to complete, this timeout is
|
||||
* applied on each transfer.
|
||||
*
|
||||
* @param LSSmaster This object.
|
||||
* @param timeout_ms timeout value in ms
|
||||
*/
|
||||
void CO_LSSmaster_changeTimeout(CO_LSSmaster_t* LSSmaster, uint16_t timeout_ms);
|
||||
|
||||
#if (((CO_CONFIG_LSS)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Initialize LSSmasterRx callback function.
|
||||
*
|
||||
* Function initializes optional callback function, which should immediately start further LSS processing. Callback is
|
||||
* called after LSS message is received from the CAN bus. It should signal the RTOS to resume corresponding task.
|
||||
*
|
||||
* @param LSSmaster 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_LSSmaster_initCallbackPre(CO_LSSmaster_t* LSSmaster, void* object, void (*pFunctSignal)(void* object));
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Request LSS switch state select
|
||||
*
|
||||
* This function can select one specific or all nodes.
|
||||
*
|
||||
* Function must be called cyclically until it returns != #CO_LSSmaster_WAIT_SLAVE Function is non-blocking.
|
||||
*
|
||||
* @remark Only one selection can be active at any time.
|
||||
*
|
||||
* @param LSSmaster This object.
|
||||
* @param timeDifference_us Time difference from previous function call in [microseconds]. Zero when request is started.
|
||||
* @param lssAddress LSS target address. If NULL, all nodes are selected
|
||||
* @return #CO_LSSmaster_ILLEGAL_ARGUMENT, #CO_LSSmaster_INVALID_STATE, #CO_LSSmaster_WAIT_SLAVE, #CO_LSSmaster_OK,
|
||||
* #CO_LSSmaster_TIMEOUT
|
||||
*/
|
||||
CO_LSSmaster_return_t CO_LSSmaster_swStateSelect(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us,
|
||||
CO_LSS_address_t* lssAddress);
|
||||
|
||||
/**
|
||||
* Request LSS switch state deselect
|
||||
*
|
||||
* This function deselects all nodes, so it doesn't matter if a specific node is selected.
|
||||
*
|
||||
* This function also resets the LSS master state machine to a clean state
|
||||
*
|
||||
* @param LSSmaster This object.
|
||||
* @return #CO_LSSmaster_ILLEGAL_ARGUMENT, #CO_LSSmaster_INVALID_STATE, #CO_LSSmaster_OK
|
||||
*/
|
||||
CO_LSSmaster_return_t CO_LSSmaster_swStateDeselect(CO_LSSmaster_t* LSSmaster);
|
||||
|
||||
/**
|
||||
* Request LSS configure Bit Timing
|
||||
*
|
||||
* The new bit rate is set as new pending value.
|
||||
*
|
||||
* This function needs one specific node to be selected.
|
||||
*
|
||||
* Function must be called cyclically until it returns != #CO_LSSmaster_WAIT_SLAVE. Function is non-blocking.
|
||||
*
|
||||
* @param LSSmaster This object.
|
||||
* @param timeDifference_us Time difference from previous function call in [microseconds]. Zero when request is started.
|
||||
* @param bit new bit rate
|
||||
* @return #CO_LSSmaster_ILLEGAL_ARGUMENT, #CO_LSSmaster_INVALID_STATE, #CO_LSSmaster_WAIT_SLAVE, #CO_LSSmaster_OK,
|
||||
* #CO_LSSmaster_TIMEOUT, #CO_LSSmaster_OK_MANUFACTURER, #CO_LSSmaster_OK_ILLEGAL_ARGUMENT
|
||||
*/
|
||||
CO_LSSmaster_return_t CO_LSSmaster_configureBitTiming(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us,
|
||||
uint16_t bit);
|
||||
|
||||
/**
|
||||
* Request LSS configure node ID
|
||||
*
|
||||
* The new node id is set as new pending node ID.
|
||||
*
|
||||
* This function needs one specific node to be selected.
|
||||
*
|
||||
* Function must be called cyclically until it returns != #CO_LSSmaster_WAIT_SLAVE. Function is non-blocking.
|
||||
*
|
||||
* @param LSSmaster This object.
|
||||
* @param timeDifference_us Time difference from previous function call in [microseconds]. Zero when request is started.
|
||||
* @param nodeId new node ID. Special value #CO_LSS_NODE_ID_ASSIGNMENT can be used to invalidate node ID.
|
||||
* @return #CO_LSSmaster_ILLEGAL_ARGUMENT, #CO_LSSmaster_INVALID_STATE, #CO_LSSmaster_WAIT_SLAVE, #CO_LSSmaster_OK,
|
||||
* #CO_LSSmaster_TIMEOUT, #CO_LSSmaster_OK_MANUFACTURER, #CO_LSSmaster_OK_ILLEGAL_ARGUMENT
|
||||
*/
|
||||
CO_LSSmaster_return_t CO_LSSmaster_configureNodeId(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us,
|
||||
uint8_t nodeId);
|
||||
|
||||
/**
|
||||
* Request LSS store configuration
|
||||
*
|
||||
* The current "pending" values for bit rate and node ID in LSS slave are stored as "permanent" values.
|
||||
*
|
||||
* This function needs one specific node to be selected.
|
||||
*
|
||||
* Function must be called cyclically until it returns != #CO_LSSmaster_WAIT_SLAVE. Function is non-blocking.
|
||||
*
|
||||
* @param LSSmaster This object.
|
||||
* @param timeDifference_us Time difference from previous function call in [microseconds]. Zero when request is started.
|
||||
* @return #CO_LSSmaster_ILLEGAL_ARGUMENT, #CO_LSSmaster_INVALID_STATE, #CO_LSSmaster_WAIT_SLAVE, #CO_LSSmaster_OK,
|
||||
* #CO_LSSmaster_TIMEOUT, #CO_LSSmaster_OK_MANUFACTURER, #CO_LSSmaster_OK_ILLEGAL_ARGUMENT
|
||||
*/
|
||||
CO_LSSmaster_return_t CO_LSSmaster_configureStore(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us);
|
||||
|
||||
/**
|
||||
* Request LSS activate bit timing
|
||||
*
|
||||
* The current "pending" bit rate in LSS slave is applied.
|
||||
*
|
||||
* Be aware that changing the bit rate is a critical step for the network. A failure will render the network unusable!
|
||||
* Therefore, this function only should be called if the following conditions are met:
|
||||
* - all nodes support changing bit timing
|
||||
* - new bit timing is successfully set as "pending" in all nodes
|
||||
* - all nodes have to activate the new bit timing roughly at the same time. Therefore this function needs all nodes
|
||||
* to be selected.
|
||||
*
|
||||
* @param LSSmaster This object.
|
||||
* @param switchDelay_ms delay that is applied by the slave once before and once after switching in ms.
|
||||
* @return #CO_LSSmaster_ILLEGAL_ARGUMENT, #CO_LSSmaster_INVALID_STATE, #CO_LSSmaster_OK
|
||||
*/
|
||||
CO_LSSmaster_return_t CO_LSSmaster_ActivateBit(CO_LSSmaster_t* LSSmaster, uint16_t switchDelay_ms);
|
||||
|
||||
/**
|
||||
* Request LSS inquire LSS address
|
||||
*
|
||||
* The LSS address value is read from the node. This is useful when the node was selected by fastscan.
|
||||
*
|
||||
* This function needs one specific node to be selected.
|
||||
*
|
||||
* Function must be called cyclically until it returns != #CO_LSSmaster_WAIT_SLAVE. Function is non-blocking.
|
||||
*
|
||||
* @param LSSmaster This object.
|
||||
* @param timeDifference_us Time difference from previous function call in [microseconds]. Zero when request is started.
|
||||
* @param [out] lssAddress read result when function returns successfully
|
||||
* @return #CO_LSSmaster_ILLEGAL_ARGUMENT, #CO_LSSmaster_INVALID_STATE, #CO_LSSmaster_WAIT_SLAVE, #CO_LSSmaster_OK,
|
||||
* #CO_LSSmaster_TIMEOUT
|
||||
*/
|
||||
CO_LSSmaster_return_t CO_LSSmaster_InquireLssAddress(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us,
|
||||
CO_LSS_address_t* lssAddress);
|
||||
|
||||
/**
|
||||
* Request LSS inquire node ID or part of LSS address
|
||||
*
|
||||
* The node-ID, identity vendor-ID, product-code, revision-number or serial-number value is read from the node.
|
||||
*
|
||||
* This function needs one specific node to be selected.
|
||||
*
|
||||
* Function must be called cyclically until it returns != #CO_LSSmaster_WAIT_SLAVE. Function is non-blocking.
|
||||
*
|
||||
* @param LSSmaster This object.
|
||||
* @param timeDifference_us Time difference from previous function call in [microseconds]. Zero when request is started.
|
||||
* @param lssInquireCs One of CO_LSS_INQUIRE_xx commands from @ref CO_LSS_command_specifiers.
|
||||
* @param [out] value read result when function returns successfully
|
||||
* @return #CO_LSSmaster_ILLEGAL_ARGUMENT, #CO_LSSmaster_INVALID_STATE, #CO_LSSmaster_WAIT_SLAVE, #CO_LSSmaster_OK,
|
||||
* #CO_LSSmaster_TIMEOUT
|
||||
*/
|
||||
CO_LSSmaster_return_t CO_LSSmaster_Inquire(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us, uint8_t lssInquireCs,
|
||||
uint32_t* value);
|
||||
|
||||
/**
|
||||
* Scan type for #CO_LSSmaster_fastscan_t scan
|
||||
*/
|
||||
typedef enum {
|
||||
CO_LSSmaster_FS_SCAN = 0, /**< Do full 32 bit scan */
|
||||
CO_LSSmaster_FS_SKIP = 1, /**< Skip this value */
|
||||
CO_LSSmaster_FS_MATCH = 2, /**< Full 32 bit value is given as argument, just verify */
|
||||
} CO_LSSmaster_scantype_t;
|
||||
|
||||
/**
|
||||
* Parameters for LSS fastscan #CO_LSSmaster_IdentifyFastscan
|
||||
*/
|
||||
typedef struct {
|
||||
CO_LSSmaster_scantype_t scan[4]; /**< Scan type for each part of the LSS address */
|
||||
CO_LSS_address_t match; /**< Value to match in case of #CO_LSSmaster_FS_MATCH */
|
||||
CO_LSS_address_t found; /**< Scan result */
|
||||
} CO_LSSmaster_fastscan_t;
|
||||
|
||||
/**
|
||||
* Select a node by LSS identify fastscan
|
||||
*
|
||||
* This initiates searching for a unconfigured node by the means of LSS fastscan mechanism. When this function is
|
||||
* finished
|
||||
* - a (more or less) arbitrary node is selected and ready for node ID assingment
|
||||
* - no node is selected because the given criteria do not match a node
|
||||
* - no node is selected because all nodes are already configured
|
||||
*
|
||||
* There are multiple ways to scan for a node. Depending on those, the scan will take different amounts of time:
|
||||
* - full scan
|
||||
* - partial scan
|
||||
* - verification
|
||||
*
|
||||
* Most of the time, those are used in combination. Consider the following example:
|
||||
* - Vendor ID and product code are known
|
||||
* - Software version doesn't matter
|
||||
* - Serial number is unknown
|
||||
*
|
||||
* In this case, the fastscan structure should be set up as following:
|
||||
* \code{.c}
|
||||
CO_LSSmaster_fastscan_t fastscan;
|
||||
fastscan.scan[CO_LSS_FASTSCAN_VENDOR_ID] = CO_LSSmaster_FS_MATCH;
|
||||
fastscan.match.vendorID = YOUR_VENDOR_ID;
|
||||
fastscan.scan[CO_LSS_FASTSCAN_PRODUCT] = CO_LSSmaster_FS_MATCH;
|
||||
fastscan.match.productCode = YOUR_PRODUCT_CODE;
|
||||
fastscan.scan[CO_LSS_FASTSCAN_REV] = CO_LSSmaster_FS_SKIP;
|
||||
fastscan.scan[CO_LSS_FASTSCAN_SERIAL] = CO_LSSmaster_FS_SCAN;
|
||||
* \endcode
|
||||
*
|
||||
* This example will take 2 scan cyles for verifying vendor ID and product code and 33 scan cycles to find the serial
|
||||
* number.
|
||||
*
|
||||
* For scanning, the following limitations apply:
|
||||
* - No more than two values can be skipped
|
||||
* - Vendor ID cannot be skipped
|
||||
*
|
||||
* @remark When doing partial scans, it is in the responsibility of the user that the LSS address is unique.
|
||||
*
|
||||
* This function needs that no node is selected when starting the scan process.
|
||||
*
|
||||
* Function must be called cyclically until it returns != #CO_LSSmaster_WAIT_SLAVE. Function is non-blocking.
|
||||
*
|
||||
* @param LSSmaster This object.
|
||||
* @param timeDifference_us Time difference from previous function call in [microseconds]. Zero when request is started.
|
||||
* @param fastscan struct according to #CO_LSSmaster_fastscan_t.
|
||||
* @return #CO_LSSmaster_ILLEGAL_ARGUMENT, #CO_LSSmaster_INVALID_STATE, #CO_LSSmaster_WAIT_SLAVE,
|
||||
* #CO_LSSmaster_SCAN_FINISHED, #CO_LSSmaster_SCAN_NOACK, #CO_LSSmaster_SCAN_FAILED
|
||||
*/
|
||||
CO_LSSmaster_return_t CO_LSSmaster_IdentifyFastscan(CO_LSSmaster_t* LSSmaster, uint32_t timeDifference_us,
|
||||
CO_LSSmaster_fastscan_t* fastscan);
|
||||
|
||||
/** @} */ /* @defgroup CO_LSSmaster */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_LSS) & CO_CONFIG_LSS_MASTER */
|
||||
|
||||
#endif /* CO_LSSmaster_H */
|
||||
429
Middleware/CANopenNode/305/CO_LSSslave.c
Normal file
429
Middleware/CANopenNode/305/CO_LSSslave.c
Normal file
@@ -0,0 +1,429 @@
|
||||
/*
|
||||
* CANopen LSS Slave protocol.
|
||||
*
|
||||
* @file CO_LSSslave.c
|
||||
* @ingroup CO_LSS
|
||||
* @author Martin Wagner
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2017 - 2020 Neuberger Gebaeudeautomation GmbH
|
||||
*
|
||||
*
|
||||
* 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 "305/CO_LSSslave.h"
|
||||
|
||||
#if ((CO_CONFIG_LSS)&CO_CONFIG_LSS_SLAVE) != 0
|
||||
|
||||
/* 'bit' must be unsigned or additional range check must be added: bit>=CO_LSS_FASTSCAN_BIT0 */
|
||||
#define CO_LSS_FASTSCAN_BITCHECK_VALID(bit) ((bit <= CO_LSS_FASTSCAN_BIT31) || (bit == CO_LSS_FASTSCAN_CONFIRM))
|
||||
/* 'index' must be unsigned or additional range check must be added: index>=CO_LSS_FASTSCAN_VENDOR_ID */
|
||||
#define CO_LSS_FASTSCAN_LSS_SUB_NEXT_VALID(index) (index <= CO_LSS_FASTSCAN_SERIAL)
|
||||
/* 'index' must be unsigned or additional range check must be added: index>=CO_LSS_BIT_TIMING_1000 */
|
||||
#define CO_LSS_BIT_TIMING_VALID(index) ((index != 5U) && (index <= CO_LSS_BIT_TIMING_AUTO))
|
||||
|
||||
/*
|
||||
* 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_LSSslave_receive(void* object, void* msg) {
|
||||
CO_LSSslave_t* LSSslave = (CO_LSSslave_t*)object;
|
||||
uint8_t DLC = CO_CANrxMsg_readDLC(msg);
|
||||
|
||||
if ((DLC == 8U) && !CO_FLAG_READ(LSSslave->sendResponse)) {
|
||||
bool_t request_LSSslave_process = false;
|
||||
const uint8_t* data = CO_CANrxMsg_readData(msg);
|
||||
uint8_t cs = data[0];
|
||||
|
||||
if (cs == CO_LSS_SWITCH_STATE_GLOBAL) {
|
||||
uint8_t mode = data[1];
|
||||
|
||||
switch (mode) {
|
||||
case CO_LSS_STATE_WAITING:
|
||||
if ((LSSslave->lssState == CO_LSS_STATE_CONFIGURATION)
|
||||
&& (LSSslave->activeNodeID == CO_LSS_NODE_ID_ASSIGNMENT)
|
||||
&& (*LSSslave->pendingNodeID != CO_LSS_NODE_ID_ASSIGNMENT)) {
|
||||
/* Slave process function will request NMT Reset comm. */
|
||||
LSSslave->service = cs;
|
||||
request_LSSslave_process = true;
|
||||
}
|
||||
LSSslave->lssState = CO_LSS_STATE_WAITING;
|
||||
(void)memset(&LSSslave->lssSelect, 0, sizeof(LSSslave->lssSelect));
|
||||
break;
|
||||
case CO_LSS_STATE_CONFIGURATION: LSSslave->lssState = CO_LSS_STATE_CONFIGURATION; break;
|
||||
default:
|
||||
/* none */
|
||||
break;
|
||||
}
|
||||
} else if (LSSslave->lssState == CO_LSS_STATE_WAITING) {
|
||||
switch (cs) {
|
||||
case CO_LSS_SWITCH_STATE_SEL_VENDOR: {
|
||||
uint32_t valSw;
|
||||
(void)memcpy((void*)(&valSw), (const void*)(&data[1]), sizeof(valSw));
|
||||
LSSslave->lssSelect.identity.vendorID = CO_SWAP_32(valSw);
|
||||
break;
|
||||
}
|
||||
case CO_LSS_SWITCH_STATE_SEL_PRODUCT: {
|
||||
uint32_t valSw;
|
||||
(void)memcpy((void*)(&valSw), (const void*)(&data[1]), sizeof(valSw));
|
||||
LSSslave->lssSelect.identity.productCode = CO_SWAP_32(valSw);
|
||||
break;
|
||||
}
|
||||
case CO_LSS_SWITCH_STATE_SEL_REV: {
|
||||
uint32_t valSw;
|
||||
(void)memcpy((void*)(&valSw), (const void*)(&data[1]), sizeof(valSw));
|
||||
LSSslave->lssSelect.identity.revisionNumber = CO_SWAP_32(valSw);
|
||||
break;
|
||||
}
|
||||
case CO_LSS_SWITCH_STATE_SEL_SERIAL: {
|
||||
uint32_t valSw;
|
||||
(void)memcpy((void*)(&valSw), (const void*)(&data[1]), sizeof(valSw));
|
||||
LSSslave->lssSelect.identity.serialNumber = CO_SWAP_32(valSw);
|
||||
|
||||
if (CO_LSS_ADDRESS_EQUAL(LSSslave->lssAddress, LSSslave->lssSelect)) {
|
||||
LSSslave->lssState = CO_LSS_STATE_CONFIGURATION;
|
||||
LSSslave->service = cs;
|
||||
request_LSSslave_process = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CO_LSS_IDENT_FASTSCAN: {
|
||||
/* fastscan is only active on unconfigured nodes */
|
||||
if ((*LSSslave->pendingNodeID == CO_LSS_NODE_ID_ASSIGNMENT)
|
||||
&& (LSSslave->activeNodeID == CO_LSS_NODE_ID_ASSIGNMENT)) {
|
||||
uint8_t bitCheck = data[5];
|
||||
uint8_t lssSub = data[6];
|
||||
uint8_t lssNext = data[7];
|
||||
uint32_t valSw;
|
||||
uint32_t idNumber;
|
||||
bool_t ack;
|
||||
|
||||
if (!CO_LSS_FASTSCAN_BITCHECK_VALID(bitCheck) || !CO_LSS_FASTSCAN_LSS_SUB_NEXT_VALID(lssSub)
|
||||
|| !CO_LSS_FASTSCAN_LSS_SUB_NEXT_VALID(lssNext)) {
|
||||
/* Invalid request */
|
||||
break;
|
||||
}
|
||||
|
||||
(void)memcpy((void*)(&valSw), (const void*)(&data[1]), sizeof(valSw));
|
||||
idNumber = CO_SWAP_32(valSw);
|
||||
ack = false;
|
||||
|
||||
if (bitCheck == CO_LSS_FASTSCAN_CONFIRM) {
|
||||
/* Confirm, Reset */
|
||||
ack = true;
|
||||
LSSslave->fastscanPos = CO_LSS_FASTSCAN_VENDOR_ID;
|
||||
(void)memset(&LSSslave->lssFastscan, 0, sizeof(LSSslave->lssFastscan));
|
||||
} else if (LSSslave->fastscanPos == lssSub) {
|
||||
uint32_t mask = 0xFFFFFFFFU << bitCheck;
|
||||
|
||||
if ((LSSslave->lssAddress.addr[lssSub] & mask) == (idNumber & mask)) {
|
||||
/* all requested bits match */
|
||||
ack = true;
|
||||
LSSslave->fastscanPos = lssNext;
|
||||
|
||||
if ((bitCheck == 0U) && (lssNext < lssSub)) {
|
||||
/* complete match, enter configuration state */
|
||||
LSSslave->lssState = CO_LSS_STATE_CONFIGURATION;
|
||||
}
|
||||
}
|
||||
} else { /* MISRA C 2004 14.10 */
|
||||
}
|
||||
if (ack) {
|
||||
#if ((CO_CONFIG_LSS)&CO_CONFIG_LSS_SLAVE_FASTSCAN_DIRECT_RESPOND) != 0
|
||||
LSSslave->TXbuff->data[0] = CO_LSS_IDENT_SLAVE;
|
||||
(void)memset(&LSSslave->TXbuff->data[1], 0, sizeof(LSSslave->TXbuff->data) - 1U);
|
||||
(void)CO_CANsend(LSSslave->CANdevTx, LSSslave->TXbuff);
|
||||
#else
|
||||
LSSslave->service = cs;
|
||||
request_LSSslave_process = true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
/* none */
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else { /* LSSslave->lssState == CO_LSS_STATE_CONFIGURATION */
|
||||
(void)memcpy((void*)(&LSSslave->CANdata[0]), (const void*)(&data[0]), sizeof(LSSslave->CANdata));
|
||||
LSSslave->service = cs;
|
||||
request_LSSslave_process = true;
|
||||
}
|
||||
|
||||
if (request_LSSslave_process) {
|
||||
CO_FLAG_SET(LSSslave->sendResponse);
|
||||
#if ((CO_CONFIG_LSS)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0
|
||||
/* Optional signal to RTOS, which can resume task, which handles further processing. */
|
||||
if (LSSslave->pFunctSignalPre != NULL) {
|
||||
LSSslave->pFunctSignalPre(LSSslave->functSignalObjectPre);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CO_ReturnError_t
|
||||
CO_LSSslave_init(CO_LSSslave_t* LSSslave, CO_LSS_address_t* lssAddress, uint16_t* pendingBitRate,
|
||||
uint8_t* pendingNodeID, CO_CANmodule_t* CANdevRx, uint16_t CANdevRxIdx, uint16_t CANidLssMaster,
|
||||
CO_CANmodule_t* CANdevTx, uint16_t CANdevTxIdx, uint16_t CANidLssSlave) {
|
||||
CO_ReturnError_t ret = CO_ERROR_NO;
|
||||
|
||||
/* verify arguments */
|
||||
if ((LSSslave == NULL) || (pendingBitRate == NULL) || (pendingNodeID == NULL) || (CANdevRx == NULL)
|
||||
|| (CANdevTx == NULL) || !CO_LSS_NODE_ID_VALID(*pendingNodeID)) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* Application must make sure that lssAddress is filled with data. */
|
||||
|
||||
/* clear the object */
|
||||
(void)memset(LSSslave, 0, sizeof(CO_LSSslave_t));
|
||||
|
||||
/* Configure object variables */
|
||||
(void)memcpy(&LSSslave->lssAddress, lssAddress, sizeof(LSSslave->lssAddress));
|
||||
LSSslave->lssState = CO_LSS_STATE_WAITING;
|
||||
LSSslave->fastscanPos = CO_LSS_FASTSCAN_VENDOR_ID;
|
||||
|
||||
LSSslave->pendingBitRate = pendingBitRate;
|
||||
LSSslave->pendingNodeID = pendingNodeID;
|
||||
LSSslave->activeNodeID = *pendingNodeID;
|
||||
CO_FLAG_CLEAR(LSSslave->sendResponse);
|
||||
|
||||
/* configure LSS CAN Master message reception */
|
||||
ret = CO_CANrxBufferInit(CANdevRx, CANdevRxIdx, CANidLssMaster, 0x7FF, false, (void*)LSSslave, CO_LSSslave_receive);
|
||||
|
||||
/* configure LSS CAN Slave response message transmission */
|
||||
LSSslave->CANdevTx = CANdevTx;
|
||||
LSSslave->TXbuff = CO_CANtxBufferInit(CANdevTx, CANdevTxIdx, CANidLssSlave, false, 8, false);
|
||||
|
||||
if (LSSslave->TXbuff == NULL) {
|
||||
ret = CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_LSS)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0
|
||||
void
|
||||
CO_LSSslave_initCallbackPre(CO_LSSslave_t* LSSslave, void* object, void (*pFunctSignalPre)(void* object)) {
|
||||
if (LSSslave != NULL) {
|
||||
LSSslave->functSignalObjectPre = object;
|
||||
LSSslave->pFunctSignalPre = pFunctSignalPre;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
CO_LSSslave_initCkBitRateCall(CO_LSSslave_t* LSSslave, void* object,
|
||||
bool_t (*pFunctLSScheckBitRate)(void* object, uint16_t bitRate)) {
|
||||
if (LSSslave != NULL) {
|
||||
LSSslave->functLSScheckBitRateObject = object;
|
||||
LSSslave->pFunctLSScheckBitRate = pFunctLSScheckBitRate;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CO_LSSslave_initActBitRateCall(CO_LSSslave_t* LSSslave, void* object,
|
||||
void (*pFunctLSSactivateBitRate)(void* object, uint16_t delay)) {
|
||||
if (LSSslave != NULL) {
|
||||
LSSslave->functLSSactivateBitRateObject = object;
|
||||
LSSslave->pFunctLSSactivateBitRate = pFunctLSSactivateBitRate;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CO_LSSslave_initCfgStoreCall(CO_LSSslave_t* LSSslave, void* object,
|
||||
bool_t (*pFunctLSScfgStore)(void* object, uint8_t id, uint16_t bitRate)) {
|
||||
if (LSSslave != NULL) {
|
||||
LSSslave->functLSScfgStoreObject = object;
|
||||
LSSslave->pFunctLSScfgStore = pFunctLSScfgStore;
|
||||
}
|
||||
}
|
||||
|
||||
bool_t
|
||||
CO_LSSslave_process(CO_LSSslave_t* LSSslave) {
|
||||
bool_t resetCommunication = false;
|
||||
|
||||
if (CO_FLAG_READ(LSSslave->sendResponse)) {
|
||||
uint8_t nid;
|
||||
uint8_t errorCode;
|
||||
uint8_t errorCodeManuf;
|
||||
uint8_t tableSelector;
|
||||
uint8_t tableIndex;
|
||||
bool_t CANsend = false;
|
||||
uint32_t valSw;
|
||||
|
||||
(void)memset(&LSSslave->TXbuff->data[0], 0, sizeof(LSSslave->TXbuff->data));
|
||||
|
||||
switch (LSSslave->service) {
|
||||
case CO_LSS_SWITCH_STATE_GLOBAL: {
|
||||
/* Node-Id was unconfigured before, now it is configured,
|
||||
* enter the NMT Reset communication autonomously. */
|
||||
resetCommunication = true;
|
||||
break;
|
||||
}
|
||||
case CO_LSS_SWITCH_STATE_SEL_SERIAL: {
|
||||
LSSslave->TXbuff->data[0] = CO_LSS_SWITCH_STATE_SEL;
|
||||
CANsend = true;
|
||||
break;
|
||||
}
|
||||
case CO_LSS_CFG_NODE_ID: {
|
||||
nid = LSSslave->CANdata[1];
|
||||
errorCode = CO_LSS_CFG_NODE_ID_OK;
|
||||
|
||||
if (CO_LSS_NODE_ID_VALID(nid)) {
|
||||
*LSSslave->pendingNodeID = nid;
|
||||
} else {
|
||||
errorCode = CO_LSS_CFG_NODE_ID_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
/* send confirmation */
|
||||
LSSslave->TXbuff->data[0] = LSSslave->service;
|
||||
LSSslave->TXbuff->data[1] = errorCode;
|
||||
/* we do not use spec-error, always 0 */
|
||||
CANsend = true;
|
||||
break;
|
||||
}
|
||||
case CO_LSS_CFG_BIT_TIMING: {
|
||||
if (LSSslave->pFunctLSScheckBitRate == NULL) {
|
||||
/* setting bit timing is not supported. Drop request */
|
||||
break;
|
||||
}
|
||||
|
||||
tableSelector = LSSslave->CANdata[1];
|
||||
tableIndex = LSSslave->CANdata[2];
|
||||
errorCode = CO_LSS_CFG_BIT_TIMING_OK;
|
||||
errorCodeManuf = CO_LSS_CFG_BIT_TIMING_OK;
|
||||
|
||||
if ((tableSelector == 0U) && CO_LSS_BIT_TIMING_VALID(tableIndex)) {
|
||||
uint16_t bit = CO_LSS_bitTimingTableLookup[tableIndex];
|
||||
bool_t bit_rate_supported = LSSslave->pFunctLSScheckBitRate(LSSslave->functLSScheckBitRateObject,
|
||||
bit);
|
||||
|
||||
if (bit_rate_supported) {
|
||||
*LSSslave->pendingBitRate = bit;
|
||||
} else {
|
||||
errorCode = CO_LSS_CFG_BIT_TIMING_MANUFACTURER;
|
||||
errorCodeManuf = CO_LSS_CFG_BIT_TIMING_OUT_OF_RANGE;
|
||||
}
|
||||
} else {
|
||||
/* we currently only support CiA301 bit timing table */
|
||||
errorCode = CO_LSS_CFG_BIT_TIMING_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
/* send confirmation */
|
||||
LSSslave->TXbuff->data[0] = LSSslave->service;
|
||||
LSSslave->TXbuff->data[1] = errorCode;
|
||||
LSSslave->TXbuff->data[2] = errorCodeManuf;
|
||||
CANsend = true;
|
||||
break;
|
||||
}
|
||||
case CO_LSS_CFG_ACTIVATE_BIT_TIMING: {
|
||||
if (LSSslave->pFunctLSScheckBitRate == NULL) {
|
||||
/* setting bit timing is not supported. Drop request */
|
||||
break;
|
||||
}
|
||||
|
||||
/* notify application */
|
||||
if (LSSslave->pFunctLSSactivateBitRate != NULL) {
|
||||
uint16_t delay = ((uint16_t)LSSslave->CANdata[2]) << 8;
|
||||
delay |= LSSslave->CANdata[1];
|
||||
LSSslave->pFunctLSSactivateBitRate(LSSslave->functLSSactivateBitRateObject, delay);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CO_LSS_CFG_STORE: {
|
||||
errorCode = CO_LSS_CFG_STORE_OK;
|
||||
|
||||
if (LSSslave->pFunctLSScfgStore == NULL) {
|
||||
/* storing is not supported. Reply error */
|
||||
errorCode = CO_LSS_CFG_STORE_NOT_SUPPORTED;
|
||||
} else {
|
||||
bool_t result;
|
||||
/* Store "pending" to "persistent" */
|
||||
result = LSSslave->pFunctLSScfgStore(LSSslave->functLSScfgStoreObject, *LSSslave->pendingNodeID,
|
||||
*LSSslave->pendingBitRate);
|
||||
if (!result) {
|
||||
errorCode = CO_LSS_CFG_STORE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/* send confirmation */
|
||||
LSSslave->TXbuff->data[0] = LSSslave->service;
|
||||
LSSslave->TXbuff->data[1] = errorCode;
|
||||
/* we do not use spec-error, always 0 */
|
||||
CANsend = true;
|
||||
break;
|
||||
}
|
||||
case CO_LSS_INQUIRE_VENDOR: {
|
||||
LSSslave->TXbuff->data[0] = LSSslave->service;
|
||||
valSw = CO_SWAP_32(LSSslave->lssAddress.identity.vendorID);
|
||||
(void)memcpy((void*)(&LSSslave->TXbuff->data[1]), (const void*)(&valSw), sizeof(valSw));
|
||||
CANsend = true;
|
||||
break;
|
||||
}
|
||||
case CO_LSS_INQUIRE_PRODUCT: {
|
||||
LSSslave->TXbuff->data[0] = LSSslave->service;
|
||||
valSw = CO_SWAP_32(LSSslave->lssAddress.identity.productCode);
|
||||
(void)memcpy((void*)(&LSSslave->TXbuff->data[1]), (const void*)(&valSw), sizeof(valSw));
|
||||
CANsend = true;
|
||||
break;
|
||||
}
|
||||
case CO_LSS_INQUIRE_REV: {
|
||||
LSSslave->TXbuff->data[0] = LSSslave->service;
|
||||
valSw = CO_SWAP_32(LSSslave->lssAddress.identity.revisionNumber);
|
||||
(void)memcpy((void*)(&LSSslave->TXbuff->data[1]), (const void*)(&valSw), sizeof(valSw));
|
||||
CANsend = true;
|
||||
break;
|
||||
}
|
||||
case CO_LSS_INQUIRE_SERIAL: {
|
||||
LSSslave->TXbuff->data[0] = LSSslave->service;
|
||||
valSw = CO_SWAP_32(LSSslave->lssAddress.identity.serialNumber);
|
||||
(void)memcpy((void*)(&LSSslave->TXbuff->data[1]), (const void*)(&valSw), sizeof(valSw));
|
||||
CANsend = true;
|
||||
break;
|
||||
}
|
||||
case CO_LSS_INQUIRE_NODE_ID: {
|
||||
LSSslave->TXbuff->data[0] = LSSslave->service;
|
||||
LSSslave->TXbuff->data[1] = LSSslave->activeNodeID;
|
||||
CANsend = true;
|
||||
break;
|
||||
}
|
||||
case CO_LSS_IDENT_FASTSCAN: {
|
||||
LSSslave->TXbuff->data[0] = CO_LSS_IDENT_SLAVE;
|
||||
CANsend = true;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
/* none */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (CANsend) {
|
||||
(void)CO_CANsend(LSSslave->CANdevTx, LSSslave->TXbuff);
|
||||
}
|
||||
|
||||
CO_FLAG_CLEAR(LSSslave->sendResponse);
|
||||
}
|
||||
|
||||
return resetCommunication;
|
||||
}
|
||||
|
||||
#endif /* (CO_CONFIG_LSS) & CO_CONFIG_LSS_SLAVE */
|
||||
238
Middleware/CANopenNode/305/CO_LSSslave.h
Normal file
238
Middleware/CANopenNode/305/CO_LSSslave.h
Normal file
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* CANopen Layer Setting Service - slave protocol.
|
||||
*
|
||||
* @file CO_LSSslave.h
|
||||
* @ingroup CO_LSS
|
||||
* @author Martin Wagner
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2017 - 2020 Neuberger Gebaeudeautomation GmbH
|
||||
*
|
||||
*
|
||||
* 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_LSSslave_H
|
||||
#define CO_LSSslave_H
|
||||
|
||||
#include "305/CO_LSS.h"
|
||||
|
||||
#if (((CO_CONFIG_LSS)&CO_CONFIG_LSS_SLAVE) != 0) || defined CO_DOXYGEN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_LSSslave LSS Slave
|
||||
* CANopen Layer Setting Service - slave protocol.
|
||||
*
|
||||
* @ingroup CO_CANopen_305
|
||||
* @{
|
||||
* The slave provides the following services
|
||||
* - node selection via LSS address
|
||||
* - node selection via LSS fastscan
|
||||
* - Inquire LSS address of currently selected node
|
||||
* - Inquire node ID
|
||||
* - Configure bit timing
|
||||
* - Configure node ID
|
||||
* - Activate bit timing parameters
|
||||
* - Store configuration (bit rate and node ID)
|
||||
*
|
||||
* After CAN module start, the LSS slave and NMT slave are started and then coexist alongside each other. To achieve
|
||||
* this behaviour, the CANopen node startup process has to be controlled more detailed. Therefore, CO_LSSinit() must be
|
||||
* invoked between CO_CANinit() and CO_CANopenInit() in the communication reset section.
|
||||
*
|
||||
* Moreover, the LSS slave needs to pause the NMT slave initialization in case no valid node ID is available at start
|
||||
* up. In that case CO_CANopenInit() skips initialization of other CANopen modules and CO_process() skips processing of
|
||||
* other modules than LSS slave automatically.
|
||||
*
|
||||
* Variables for CAN-bitrate and CANopen node-id must be initialized by application from non-volatile memory or dip
|
||||
* switches. Pointers to them are passed to CO_LSSinit() function. Those variables represents pending values. If node-id
|
||||
* is valid in the moment it enters CO_LSSinit(), it also becomes active node-id and the stack initialises normally.
|
||||
* Otherwise, node-id must be configured by lss and after successful configuration stack passes reset communication
|
||||
* autonomously.
|
||||
*
|
||||
* Device with all threads can be normally initialized and running despite that node-id is not valid. Application must
|
||||
* take care, because CANopen is not initialized. In that case CO_CANopenInit() returns error condition
|
||||
* CO_ERROR_NODE_ID_UNCONFIGURED_LSS which must be handled properly. Status can also be checked with
|
||||
* CO->nodeIdUnconfigured variable.
|
||||
*
|
||||
* Some callback functions may be initialized by application with CO_LSSslave_initCkBitRateCall(),
|
||||
* CO_LSSslave_initActBitRateCall() and CO_LSSslave_initCfgStoreCall().
|
||||
*/
|
||||
|
||||
/**
|
||||
* LSS slave object.
|
||||
*/
|
||||
typedef struct {
|
||||
CO_LSS_address_t lssAddress; /**< From #CO_LSSslave_init */
|
||||
uint8_t lssState; /**< @ref CO_LSS_STATE_state */
|
||||
CO_LSS_address_t lssSelect; /**< Received LSS Address by select */
|
||||
CO_LSS_address_t lssFastscan; /**< Received LSS Address by fastscan */
|
||||
uint8_t fastscanPos; /**< Current state of fastscan */
|
||||
uint16_t* pendingBitRate; /**< Bit rate value that is temporarily configured */
|
||||
uint8_t* pendingNodeID; /**< Node ID that is temporarily configured */
|
||||
uint8_t activeNodeID; /**< Node ID used at the CAN interface */
|
||||
volatile void*
|
||||
sendResponse; /**< Variable indicates, if LSS response has to be sent by mainline processing function */
|
||||
uint8_t service; /**< Service, which will have to be processed by mainline processing function */
|
||||
uint8_t CANdata[8]; /**< Received CAN data, which will be processed by mainline processing function */
|
||||
|
||||
#if (((CO_CONFIG_LSS)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
void (*pFunctSignalPre)(void* object); /**< From CO_LSSslave_initCallbackPre() or NULL */
|
||||
void* functSignalObjectPre; /**< Pointer to object */
|
||||
#endif
|
||||
bool_t (*pFunctLSScheckBitRate)(void* object,
|
||||
uint16_t bitRate); /**< From CO_LSSslave_initCkBitRateCall() or NULL */
|
||||
void* functLSScheckBitRateObject; /** Pointer to object */
|
||||
void (*pFunctLSSactivateBitRate)(
|
||||
void* object, uint16_t delay); /**< From CO_LSSslave_initActBitRateCall() or NULL. Delay is in ms */
|
||||
void* functLSSactivateBitRateObject; /** Pointer to object */
|
||||
bool_t (*pFunctLSScfgStore)(void* object, uint8_t id,
|
||||
uint16_t bitRate); /**< From CO_LSSslave_initCfgStoreCall() or NULL */
|
||||
void* functLSScfgStoreObject; /** Pointer to object */
|
||||
CO_CANmodule_t* CANdevTx; /**< From #CO_LSSslave_init() */
|
||||
CO_CANtx_t* TXbuff; /**< CAN transmit buffer */
|
||||
} CO_LSSslave_t;
|
||||
|
||||
/**
|
||||
* Initialize LSS object.
|
||||
*
|
||||
* Function must be called in the communication reset section.
|
||||
*
|
||||
* pendingBitRate and pendingNodeID must be pointers to external variables. Both variables must be initialized on
|
||||
* program startup (after #CO_NMT_RESET_NODE) from non-volatile memory, dip switches or similar. They must not change
|
||||
* during #CO_NMT_RESET_COMMUNICATION. Both variables can be changed by CO_LSSslave_process(), depending on commands
|
||||
* from the LSS master.
|
||||
*
|
||||
* If pendingNodeID is valid (1 <= pendingNodeID <= 0x7F), then this becomes valid active nodeId just after exit of this
|
||||
* function. In that case all other CANopen objects may be initialized and processed in run time.
|
||||
*
|
||||
* If pendingNodeID is not valid (pendingNodeID == 0xFF), then only LSS slave is initialized and processed in run time.
|
||||
* In that state pendingNodeID can be configured and after successful configuration reset communication with all CANopen
|
||||
* object is activated automatically.
|
||||
*
|
||||
* @remark The LSS address needs to be unique on the network. For this, the 128 bit wide identity object (1018h) is
|
||||
* used. Therefore, this object has to be fully initialized before passing it to this function (vendorID, product code,
|
||||
* revisionNo, serialNo are set to 0 by default). Otherwise, if non-configured devices are present on CANopen network,
|
||||
* LSS configuration may behave unpredictable.
|
||||
*
|
||||
* @param LSSslave This object will be initialized.
|
||||
* @param lssAddress LSS address
|
||||
* @param [in,out] pendingBitRate Pending bit rate of the CAN interface
|
||||
* @param [in,out] pendingNodeID Pending node ID or 0xFF - invalid
|
||||
* @param CANdevRx CAN device for LSS slave reception.
|
||||
* @param CANdevRxIdx Index of receive buffer in the above CAN device.
|
||||
* @param CANidLssMaster COB ID for reception.
|
||||
* @param CANdevTx CAN device for LSS slave transmission.
|
||||
* @param CANdevTxIdx Index of transmit buffer in the above CAN device.
|
||||
* @param CANidLssSlave COB ID for transmission.
|
||||
* @return #CO_ReturnError_t: CO_ERROR_NO or CO_ERROR_ILLEGAL_ARGUMENT.
|
||||
*/
|
||||
CO_ReturnError_t CO_LSSslave_init(CO_LSSslave_t* LSSslave, CO_LSS_address_t* lssAddress, uint16_t* pendingBitRate,
|
||||
uint8_t* pendingNodeID, CO_CANmodule_t* CANdevRx, uint16_t CANdevRxIdx,
|
||||
uint16_t CANidLssMaster, CO_CANmodule_t* CANdevTx, uint16_t CANdevTxIdx,
|
||||
uint16_t CANidLssSlave);
|
||||
|
||||
/**
|
||||
* Process LSS communication
|
||||
*
|
||||
* Object is partially pre-processed after LSS message received. Further processing is inside this function.
|
||||
*
|
||||
* In case that Node-Id is unconfigured, then this function may request CANopen communication reset. This happens, when
|
||||
* valid node-id is configured by LSS master.
|
||||
*
|
||||
* @param LSSslave This object.
|
||||
* @return True, if #CO_NMT_RESET_COMMUNICATION is requested
|
||||
*/
|
||||
bool_t CO_LSSslave_process(CO_LSSslave_t* LSSslave);
|
||||
|
||||
/**
|
||||
* Get current LSS state
|
||||
*
|
||||
* @param LSSslave This object.
|
||||
* @return @ref CO_LSS_STATE_state
|
||||
*/
|
||||
static inline uint8_t
|
||||
CO_LSSslave_getState(CO_LSSslave_t* LSSslave) {
|
||||
return (LSSslave == NULL) ? CO_LSS_STATE_WAITING : LSSslave->lssState;
|
||||
}
|
||||
|
||||
#if (((CO_CONFIG_LSS)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Initialize LSSslaveRx callback function.
|
||||
*
|
||||
* Function initializes optional callback function, which should immediately start further LSS processing. Callback is
|
||||
* called after LSS message is received from the CAN bus. It should signal the RTOS to resume corresponding task.
|
||||
*
|
||||
* @param LSSslave This object.
|
||||
* @param object Pointer to object, which will be passed to pFunctSignal(). Can be NULL
|
||||
* @param pFunctSignalPre Pointer to the callback function. Not called if NULL.
|
||||
*/
|
||||
void CO_LSSslave_initCallbackPre(CO_LSSslave_t* LSSslave, void* object, void (*pFunctSignalPre)(void* object));
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initialize verify bit rate callback
|
||||
*
|
||||
* Function initializes callback function, which is called when "config bit timing parameters" is used. The callback
|
||||
* function needs to check if the new bit rate is supported by the CANopen device. Callback returns "true" if supported.
|
||||
* When no callback is set the LSS slave will no-ack the request, indicating to the master that bit rate change is not
|
||||
* supported.
|
||||
*
|
||||
* @param LSSslave This object.
|
||||
* @param object Pointer to object, which will be passed to pFunctLSScheckBitRate(). Can be NULL
|
||||
* @param pFunctLSScheckBitRate Pointer to the callback function. Not called if NULL.
|
||||
*/
|
||||
void CO_LSSslave_initCkBitRateCall(CO_LSSslave_t* LSSslave, void* object,
|
||||
bool_t (*pFunctLSScheckBitRate)(void* object, uint16_t bitRate));
|
||||
|
||||
/**
|
||||
* Initialize activate bit rate callback
|
||||
*
|
||||
* Function initializes callback function, which is called when "activate bit timing parameters" is used. The callback
|
||||
* function gives the user an event to allow setting a timer or do calculations based on the exact time the request
|
||||
* arrived. According to DSP 305 6.4.4, the delay has to be applied once before and once after switching bit rates.
|
||||
* During this time, a device mustn't send any messages.
|
||||
*
|
||||
* @param LSSslave This object.
|
||||
* @param object Pointer to object, which will be passed to pFunctLSSactivateBitRate(). Can be NULL
|
||||
* @param pFunctLSSactivateBitRate Pointer to the callback function. Not called if NULL.
|
||||
*/
|
||||
void CO_LSSslave_initActBitRateCall(CO_LSSslave_t* LSSslave, void* object,
|
||||
void (*pFunctLSSactivateBitRate)(void* object, uint16_t delay));
|
||||
|
||||
/**
|
||||
* Store configuration callback
|
||||
*
|
||||
* Function initializes callback function, which is called when "store configuration" is used. The callback function
|
||||
* gives the user an event to store the corresponding node id and bit rate to NVM. Those values have to be supplied to
|
||||
* the init function as "persistent values" after reset. If callback returns "true", success is send to the LSS master.
|
||||
* When no callback is set the LSS slave will no-ack the request, indicating to the master that storing is not
|
||||
* supported.
|
||||
*
|
||||
* @param LSSslave This object.
|
||||
* @param object Pointer to object, which will be passed to pFunctLSScfgStore(). Can be NULL
|
||||
* @param pFunctLSScfgStore Pointer to the callback function. Not called if NULL.
|
||||
*/
|
||||
void CO_LSSslave_initCfgStoreCall(CO_LSSslave_t* LSSslave, void* object,
|
||||
bool_t (*pFunctLSScfgStore)(void* object, uint8_t id, uint16_t bitRate));
|
||||
|
||||
/** @} */ /* @defgroup CO_LSSslave */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_LSS) & CO_CONFIG_LSS_SLAVE */
|
||||
|
||||
#endif /* CO_LSSslave_H */
|
||||
2009
Middleware/CANopenNode/309/CO_gateway_ascii.c
Normal file
2009
Middleware/CANopenNode/309/CO_gateway_ascii.c
Normal file
File diff suppressed because it is too large
Load Diff
410
Middleware/CANopenNode/309/CO_gateway_ascii.h
Normal file
410
Middleware/CANopenNode/309/CO_gateway_ascii.h
Normal file
@@ -0,0 +1,410 @@
|
||||
/**
|
||||
* CANopen access from other networks - ASCII mapping (CiA 309-3 DS v3.0.0)
|
||||
*
|
||||
* @file CO_gateway_ascii.h
|
||||
* @ingroup CO_CANopen_309_3
|
||||
* @author Janez Paternoster
|
||||
* @author Martin Wagner
|
||||
* @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_GATEWAY_ASCII_H
|
||||
#define CO_GATEWAY_ASCII_H
|
||||
|
||||
#include "301/CO_driver.h"
|
||||
#include "301/CO_fifo.h"
|
||||
#include "301/CO_SDOclient.h"
|
||||
#include "301/CO_NMT_Heartbeat.h"
|
||||
#include "305/CO_LSSmaster.h"
|
||||
#include "303/CO_LEDs.h"
|
||||
|
||||
/* default configuration, see CO_config.h */
|
||||
#ifndef CO_CONFIG_GTW
|
||||
#define CO_CONFIG_GTW (0)
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_GTW)&CO_CONFIG_GTW_ASCII) != 0) || defined CO_DOXYGEN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_CANopen_309_3 Gateway ASCII mapping
|
||||
* CANopen access from other networks - ASCII mapping (CiA 309-3 DSP v3.0.0)
|
||||
*
|
||||
* @ingroup CO_CANopen_309
|
||||
* @{
|
||||
* This module enables ascii command interface (CAN gateway), which can be used for master interaction with CANopen
|
||||
* network. Some sort of string input/output stream can be used, for example serial port + terminal on microcontroller
|
||||
* or stdio in OS or sockets, etc.
|
||||
*
|
||||
* For example, one wants to read 'Heartbeat producer time' parameter (0x1017,0) on remote node (with id=4). Parameter
|
||||
* is 16-bit integer. He can can enter command string: `[1] 4 read 0x1017 0 i16`. CANopenNode will use SDO client, send
|
||||
* request to remote node via CAN, wait for response via CAN and prints `[1] OK` to output stream on success.
|
||||
*
|
||||
* This module is usually initialized and processed in CANopen.c file. Application should register own callback function
|
||||
* for reading the output stream. Application writes new commands with CO_GTWA_write().
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup CO_CANopen_309_3_Syntax Command syntax
|
||||
* ASCII command syntax.
|
||||
*
|
||||
* @{
|
||||
*
|
||||
* @code{.unparsed}
|
||||
Command strings start with '"["<sequence>"]"' followed by:
|
||||
[[<net>] <node>] r[ead] <index> <subindex> [<datatype>] # SDO upload.
|
||||
[[<net>] <node>] w[rite] <index> <subindex> <datatype> <value> # SDO download.
|
||||
|
||||
[[<net>] <node>] start # NMT Start node.
|
||||
[[<net>] <node>] stop # NMT Stop node.
|
||||
[[<net>] <node>] preop[erational] # NMT Set node to pre-operational.
|
||||
[[<net>] <node>] reset node # NMT Reset node.
|
||||
[[<net>] <node>] reset comm[unication] # NMT Reset communication.
|
||||
|
||||
[<net>] set network <value> # Set default net.
|
||||
[<net>] set node <value> # Set default node.
|
||||
[<net>] set sdo_timeout <value> # Configure SDO time-out.
|
||||
[<net>] set sdo_block <value> # Enable/disable SDO block transfer.
|
||||
|
||||
help [datatype|lss] # Print this or datatype or lss help.
|
||||
led # Print status LED diodes.
|
||||
log # Print message log.
|
||||
|
||||
Response:
|
||||
"["<sequence>"]" OK | <value> |
|
||||
ERROR:<SDO-abort-code> | ERROR:<internal-error-code>
|
||||
|
||||
* Every command must be terminated with <CR><LF> ('\\r\\n'). characters. Same
|
||||
is response. String is not null terminated, <CR> is optional in command.
|
||||
* Comments started with '#' are ignored. They may be on the beginning of the
|
||||
line or after the command string.
|
||||
* 'sdo_timeout' is in milliseconds, 500 by default. Block transfer is
|
||||
disabled by default.
|
||||
* If '<net>' or '<node>' is not specified within commands, then value defined
|
||||
by 'set network' or 'set node' command is used.
|
||||
|
||||
Datatypes:
|
||||
b # Boolean.
|
||||
i8, i16, i32, i64 # Signed integers.
|
||||
u8, u16, u32, u64 # Unsigned integers.
|
||||
x8, x16, x32, x64 # Unsigned integers, displayed as hexadecimal, non-standard.
|
||||
r32, r64 # Real numbers.
|
||||
t, td # Time of day, time difference.
|
||||
vs # Visible string (between double quotes if multi-word).
|
||||
os, us # Octet, unicode string, (mime-base64 (RFC2045) based, line).
|
||||
d # domain (mime-base64 (RFC2045) based, one line).
|
||||
hex # Hexagonal data, optionally space separated, non-standard.
|
||||
|
||||
LSS commands:
|
||||
lss_switch_glob <0|1> # Switch state global command.
|
||||
lss_switch_sel <vendorID> <product code> \\
|
||||
<revisionNo> <serialNo> #Switch state selective.
|
||||
lss_set_node <node> # Configure node-ID.
|
||||
lss_conf_bitrate <table_selector=0> \\
|
||||
<table_index> # Configure bit-rate.
|
||||
lss_activate_bitrate <switch_delay_ms> # Activate new bit-rate.
|
||||
lss_store # LSS store configuration.
|
||||
lss_inquire_addr [<LSSSUB=0..3>] # Inquire LSS address.
|
||||
lss_get_node # Inquire node-ID.
|
||||
_lss_fastscan [<timeout_ms>] # Identify fastscan, non-standard.
|
||||
lss_allnodes [<timeout_ms> [<nodeStart=1..127> <store=0|1>\\
|
||||
[<scanType0> <vendorId> <scanType1> <productCode>\\
|
||||
<scanType2> <revisionNo> <scanType3> <serialNo>]]]
|
||||
# Node-ID configuration of all nodes.
|
||||
|
||||
* All LSS commands start with '\"[\"<sequence>\"]\" [<net>]'.
|
||||
* <table_index>: 0=1000 kbit/s, 1=800 kbit/s, 2=500 kbit/s, 3=250 kbit/s,
|
||||
4=125 kbit/s, 6=50 kbit/s, 7=20 kbit/s, 8=10 kbit/s, 9=auto
|
||||
* <scanType>: 0=fastscan, 1=ignore, 2=match value in next parameter
|
||||
* @endcode
|
||||
*
|
||||
* This help text is the same as variable contents in CO_GTWA_helpString.
|
||||
* @}
|
||||
*/
|
||||
|
||||
/** Size of response string buffer. This is intermediate buffer. If there is larger amount of data to transfer, then
|
||||
* multiple transfers will occur. */
|
||||
#ifndef CO_GTWA_RESP_BUF_SIZE
|
||||
#define CO_GTWA_RESP_BUF_SIZE 200U
|
||||
#endif
|
||||
|
||||
/** Timeout time in microseconds for some internal states. */
|
||||
#ifndef CO_GTWA_STATE_TIMEOUT_TIME_US
|
||||
#define CO_GTWA_STATE_TIMEOUT_TIME_US 1200000U
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Response error codes as specified by CiA 309-3. Values less or equal to 0 are used for control for some functions and
|
||||
* are not part of the standard.
|
||||
*/
|
||||
typedef enum {
|
||||
CO_GTWA_respErrorNone = 0, /**< 0 - No error or idle */
|
||||
CO_GTWA_respErrorReqNotSupported = 100, /**< 100 - Request not supported */
|
||||
CO_GTWA_respErrorSyntax = 101, /**< 101 - Syntax error */
|
||||
CO_GTWA_respErrorInternalState = 102, /**< 102 - Request not processed due to internal state */
|
||||
CO_GTWA_respErrorTimeOut = 103, /**< 103 - Time-out (where applicable) */
|
||||
CO_GTWA_respErrorNoDefaultNetSet = 104, /**< 104 - No default net set */
|
||||
CO_GTWA_respErrorNoDefaultNodeSet = 105, /**< 105 - No default node set */
|
||||
CO_GTWA_respErrorUnsupportedNet = 106, /**< 106 - Unsupported net */
|
||||
CO_GTWA_respErrorUnsupportedNode = 107, /**< 107 - Unsupported node */
|
||||
CO_GTWA_respErrorLostGuardingMessage = 200, /**< 200 - Lost guarding message */
|
||||
CO_GTWA_respErrorLostConnection = 201, /**< 201 - Lost connection */
|
||||
CO_GTWA_respErrorHeartbeatStarted = 202, /**< 202 - Heartbeat started */
|
||||
CO_GTWA_respErrorHeartbeatLost = 203, /**< 203 - Heartbeat lost */
|
||||
CO_GTWA_respErrorWrongNMTstate = 204, /**< 204 - Wrong NMT state */
|
||||
CO_GTWA_respErrorBootUp = 205, /**< 205 - Boot-up */
|
||||
CO_GTWA_respErrorErrorPassive = 300, /**< 300 - Error passive */
|
||||
CO_GTWA_respErrorBusOff = 301, /**< 301 - Bus off */
|
||||
CO_GTWA_respErrorCANbufferOverflow = 303, /**< 303 - CAN buffer overflow */
|
||||
CO_GTWA_respErrorCANinit = 304, /**< 304 - CAN init */
|
||||
CO_GTWA_respErrorCANactive = 305, /**< 305 - CAN active (at init or start-up) */
|
||||
CO_GTWA_respErrorPDOalreadyUsed = 400, /**< 400 - PDO already used */
|
||||
CO_GTWA_respErrorPDOlengthExceeded = 401, /**< 401 - PDO length exceeded */
|
||||
CO_GTWA_respErrorLSSmanufacturer = 501, /**< 501 - LSS implementation- / manufacturer-specific error */
|
||||
CO_GTWA_respErrorLSSnodeIdNotSupported = 502, /**< 502 - LSS node-ID not supported */
|
||||
CO_GTWA_respErrorLSSbitRateNotSupported = 503, /**< 503 - LSS bit-rate not supported */
|
||||
CO_GTWA_respErrorLSSparameterStoringFailed = 504, /**< 504 - LSS parameter storing failed */
|
||||
CO_GTWA_respErrorLSSmediaError = 505, /**< 505 - LSS command failed because of media error */
|
||||
CO_GTWA_respErrorRunningOutOfMemory = 600 /**< 600 - Running out of memory */
|
||||
} CO_GTWA_respErrorCode_t;
|
||||
|
||||
/**
|
||||
* Internal states of the Gateway-ascii state machine.
|
||||
*/
|
||||
typedef enum {
|
||||
CO_GTWA_ST_IDLE = 0x00U, /**< Gateway is idle, no command is processing. This state is starting point for new
|
||||
commands, which are parsed here. */
|
||||
CO_GTWA_ST_READ = 0x10U, /**< SDO 'read' (upload) */
|
||||
CO_GTWA_ST_WRITE = 0x11U, /**< SDO 'write' (download) */
|
||||
CO_GTWA_ST_WRITE_ABORTED = 0x12U, /**< SDO 'write' (download) - aborted, purging remaining data */
|
||||
CO_GTWA_ST_LSS_SWITCH_GLOB = 0x20U, /**< LSS 'lss_switch_glob' */
|
||||
CO_GTWA_ST_LSS_SWITCH_SEL = 0x21U, /**< LSS 'lss_switch_sel' */
|
||||
CO_GTWA_ST_LSS_SET_NODE = 0x22U, /**< LSS 'lss_set_node' */
|
||||
CO_GTWA_ST_LSS_CONF_BITRATE = 0x23U, /**< LSS 'lss_conf_bitrate' */
|
||||
CO_GTWA_ST_LSS_STORE = 0x24U, /**< LSS 'lss_store' */
|
||||
CO_GTWA_ST_LSS_INQUIRE = 0x25U, /**< LSS 'lss_inquire_addr' or 'lss_get_node' */
|
||||
CO_GTWA_ST_LSS_INQUIRE_ADDR_ALL = 0x26U, /**< LSS 'lss_inquire_addr', all parameters */
|
||||
CO_GTWA_ST__LSS_FASTSCAN = 0x30U, /**< LSS '_lss_fastscan' */
|
||||
CO_GTWA_ST_LSS_ALLNODES = 0x31U, /**< LSS 'lss_allnodes' */
|
||||
CO_GTWA_ST_LOG = 0x80U, /**< print message 'log' */
|
||||
CO_GTWA_ST_HELP = 0x81U, /**< print 'help' text */
|
||||
CO_GTWA_ST_LED = 0x82U /**< print 'status' of the node */
|
||||
} CO_GTWA_state_t;
|
||||
|
||||
#if (((CO_CONFIG_GTW)&CO_CONFIG_GTW_ASCII_SDO) != 0) || defined CO_DOXYGEN
|
||||
/*
|
||||
* CANopen Gateway-ascii data types structure
|
||||
*/
|
||||
typedef struct {
|
||||
char* syntax; /**< Data type syntax, as defined in CiA309-3 */
|
||||
size_t length; /**< Data type length in bytes, 0 if size is not known */
|
||||
/** Function, which reads data of specific data type from fifo buffer and writes them as corresponding ascii string.
|
||||
* It is a pointer to #CO_fifo_readU82a function or similar and is used with SDO upload. For description of
|
||||
* parameters see #CO_fifo_readU82a */
|
||||
size_t (*dataTypePrint)(CO_fifo_t* fifo, char* buf, size_t count, bool_t end);
|
||||
/** Function, which reads ascii-data of specific data type from fifo buffer and copies them to another fifo buffer
|
||||
* as binary data. It is a pointer to #CO_fifo_cpyTok2U8 function or similar and is used with SDO download. For
|
||||
* description of parameters see #CO_fifo_cpyTok2U8 */
|
||||
size_t (*dataTypeScan)(CO_fifo_t* dest, CO_fifo_t* src, uint8_t* status);
|
||||
} CO_GTWA_dataType_t;
|
||||
#endif /* (CO_CONFIG_GTW) & CO_CONFIG_GTW_ASCII_SDO */
|
||||
|
||||
/**
|
||||
* CANopen Gateway-ascii object
|
||||
*/
|
||||
typedef struct {
|
||||
/** Pointer to external function for reading response from Gateway-ascii object. Pointer is initialized in
|
||||
* CO_GTWA_initRead().
|
||||
*
|
||||
* @param object Void pointer to custom object
|
||||
* @param buf Buffer from which data can be read
|
||||
* @param count Count of bytes available inside buffer
|
||||
* @param [out] connectionOK different than 0 indicates connection is OK.
|
||||
*
|
||||
* @return Count of bytes actually transferred.
|
||||
*/
|
||||
size_t (*readCallback)(void* object, const char* buf, size_t count, uint8_t* connectionOK);
|
||||
void* readCallbackObject; /**< Pointer to object, which will be used inside readCallback, from CO_GTWA_init() */
|
||||
uint32_t sequence; /**< Sequence number of the command */
|
||||
int32_t net_default; /**< Default CANopen Net number is undefined (-1) at startup */
|
||||
int16_t node_default; /**< Default CANopen Node ID number is undefined (-1) at startup */
|
||||
uint16_t net; /**< Current CANopen Net number */
|
||||
uint8_t node; /**< Current CANopen Node ID */
|
||||
CO_fifo_t commFifo; /**< CO_fifo_t object for command (not pointer) */
|
||||
uint8_t commBuf[CO_CONFIG_GTWA_COMM_BUF_SIZE + 1]; /**< Command buffer of usable size
|
||||
@ref CO_CONFIG_GTWA_COMM_BUF_SIZE */
|
||||
char respBuf[CO_GTWA_RESP_BUF_SIZE]; /**< Response buffer of usable size @ref CO_GTWA_RESP_BUF_SIZE */
|
||||
size_t respBufCount; /**< Actual size of data in respBuf */
|
||||
size_t respBufOffset; /**< If only part of data has been successfully written into external application (with
|
||||
readCallback()), then Gateway-ascii object will stay in current state. This situation is
|
||||
indicated with respHold variable and respBufOffset indicates offset to untransferred data
|
||||
inside respBuf. */
|
||||
bool_t respHold; /**< See respBufOffset above */
|
||||
uint32_t timeDifference_us_cumulative; /**< Sum of time difference from CO_GTWA_process() in case of respHold */
|
||||
CO_GTWA_state_t state; /**< Current state of the gateway object */
|
||||
uint32_t stateTimeoutTmr; /**< Timeout timer for the current state */
|
||||
#if (((CO_CONFIG_GTW)&CO_CONFIG_GTW_ASCII_SDO) != 0) || defined CO_DOXYGEN
|
||||
CO_SDOclient_t* SDO_C; /**< SDO client object from CO_GTWA_init() */
|
||||
uint16_t SDOtimeoutTime; /**< Timeout time for SDO transfer in milliseconds, if no response */
|
||||
bool_t SDOblockTransferEnable; /**< SDO block transfer enabled? */
|
||||
bool_t SDOdataCopyStatus; /**< Indicate status of data copy from / to SDO buffer. If reading, true indicates, that
|
||||
response has started. If writing, true indicates, that SDO buffer contains only part of
|
||||
data and more data will follow. */
|
||||
const CO_GTWA_dataType_t* SDOdataType; /**< Data type of variable in current SDO communication */
|
||||
#endif
|
||||
#if (((CO_CONFIG_GTW)&CO_CONFIG_GTW_ASCII_NMT) != 0) || defined CO_DOXYGEN
|
||||
CO_NMT_t* NMT; /**< NMT object from CO_GTWA_init() */
|
||||
#endif
|
||||
#if (((CO_CONFIG_GTW)&CO_CONFIG_GTW_ASCII_LSS) != 0) || defined CO_DOXYGEN
|
||||
CO_LSSmaster_t* LSSmaster; /**< LSSmaster object from CO_GTWA_init() */
|
||||
CO_LSS_address_t lssAddress; /**< 128 bit number, uniquely identifying each node */
|
||||
uint8_t lssNID; /**< LSS Node-ID parameter */
|
||||
uint16_t lssBitrate; /**< LSS bitrate parameter */
|
||||
uint8_t lssInquireCs; /**< LSS inquire parameter */
|
||||
CO_LSSmaster_fastscan_t lssFastscan; /**< LSS fastscan parameter */
|
||||
uint8_t lssSubState; /**< LSS allnodes sub state parameter */
|
||||
uint8_t lssNodeCount; /**< LSS allnodes node count parameter */
|
||||
bool_t lssStore; /**< LSS allnodes store parameter */
|
||||
uint16_t lssTimeout_ms; /**< LSS allnodes timeout parameter */
|
||||
#endif
|
||||
#if (((CO_CONFIG_GTW)&CO_CONFIG_GTW_ASCII_LOG) != 0) || defined CO_DOXYGEN
|
||||
uint8_t logBuf[CO_CONFIG_GTWA_LOG_BUF_SIZE + 1]; /**< Message log buffer of usable size
|
||||
@ref CO_CONFIG_GTWA_LOG_BUF_SIZE */
|
||||
CO_fifo_t logFifo; /**< CO_fifo_t object for message log (not pointer) */
|
||||
#endif
|
||||
#if (((CO_CONFIG_GTW)&CO_CONFIG_GTW_ASCII_PRINT_HELP) != 0) || defined CO_DOXYGEN
|
||||
const char* helpString; /**< Offset, when printing help text */
|
||||
size_t helpStringOffset;
|
||||
#endif
|
||||
#if (((CO_CONFIG_GTW)&CO_CONFIG_GTW_ASCII_PRINT_LEDS) != 0) || defined CO_DOXYGEN
|
||||
CO_LEDs_t* LEDs; /**< CO_LEDs_t object for CANopen status LEDs imitation from CO_GTWA_init() */
|
||||
uint8_t ledStringPreviousIndex;
|
||||
#endif
|
||||
} CO_GTWA_t;
|
||||
|
||||
/**
|
||||
* Initialize Gateway-ascii object
|
||||
*
|
||||
* @param gtwa This object will be initialized
|
||||
* @param SDO_C SDO client object
|
||||
* @param SDOclientTimeoutTime_ms Default timeout in milliseconds, 500 typically
|
||||
* @param SDOclientBlockTransfer If true, block transfer will be set by default
|
||||
* @param NMT NMT object
|
||||
* @param LSSmaster LSS master object
|
||||
* @param LEDs LEDs object
|
||||
* @param dummy dummy argument, set to 0
|
||||
*
|
||||
* @return #CO_ReturnError_t: CO_ERROR_NO or CO_ERROR_ILLEGAL_ARGUMENT
|
||||
*/
|
||||
CO_ReturnError_t CO_GTWA_init(CO_GTWA_t* gtwa,
|
||||
#if (((CO_CONFIG_GTW)&CO_CONFIG_GTW_ASCII_SDO) != 0) || defined CO_DOXYGEN
|
||||
CO_SDOclient_t* SDO_C, uint16_t SDOclientTimeoutTime_ms, bool_t SDOclientBlockTransfer,
|
||||
#endif
|
||||
#if (((CO_CONFIG_GTW)&CO_CONFIG_GTW_ASCII_NMT) != 0) || defined CO_DOXYGEN
|
||||
CO_NMT_t* NMT,
|
||||
#endif
|
||||
#if (((CO_CONFIG_GTW)&CO_CONFIG_GTW_ASCII_LSS) != 0) || defined CO_DOXYGEN
|
||||
CO_LSSmaster_t* LSSmaster,
|
||||
#endif
|
||||
#if (((CO_CONFIG_GTW)&CO_CONFIG_GTW_ASCII_PRINT_LEDS) != 0) || defined CO_DOXYGEN
|
||||
CO_LEDs_t* LEDs,
|
||||
#endif
|
||||
uint8_t dummy);
|
||||
|
||||
/**
|
||||
* Initialize read callback in Gateway-ascii object
|
||||
*
|
||||
* Callback will be used for transfer data to output stream of the application. It will be called from CO_GTWA_process()
|
||||
* zero or multiple times, depending on the data available. If readCallback is uninitialized or NULL, then output data
|
||||
* will be purged.
|
||||
*
|
||||
* @param gtwa This object will be initialized
|
||||
* @param readCallback Pointer to external function for reading response from Gateway-ascii object. See #CO_GTWA_t for
|
||||
* parameters.
|
||||
* @param readCallbackObject Pointer to object, which will be used inside readCallback
|
||||
*/
|
||||
void CO_GTWA_initRead(CO_GTWA_t* gtwa,
|
||||
size_t (*readCallback)(void* object, const char* buf, size_t count, uint8_t* connectionOK),
|
||||
void* readCallbackObject);
|
||||
|
||||
/**
|
||||
* Get free write buffer space
|
||||
*
|
||||
* @param gtwa This object
|
||||
*
|
||||
* @return number of available bytes
|
||||
*/
|
||||
static inline size_t
|
||||
CO_GTWA_write_getSpace(CO_GTWA_t* gtwa) {
|
||||
return CO_fifo_getSpace(>wa->commFifo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write command into CO_GTWA_t object.
|
||||
*
|
||||
* This function copies ascii command from buf into internal fifo buffer. Command must be closed with '\n' character.
|
||||
* Function returns number of bytes successfully copied. If there is not enough space in destination, not all bytes will
|
||||
* be copied and data can be refilled later (in case of large SDO download).
|
||||
*
|
||||
* @param gtwa This object
|
||||
* @param buf Buffer which will be copied
|
||||
* @param count Number of bytes in buf
|
||||
*
|
||||
* @return number of bytes actually written.
|
||||
*/
|
||||
static inline size_t
|
||||
CO_GTWA_write(CO_GTWA_t* gtwa, const char* buf, size_t count) {
|
||||
return CO_fifo_write(>wa->commFifo, (const uint8_t*)buf, count, NULL);
|
||||
}
|
||||
|
||||
#if (((CO_CONFIG_GTW)&CO_CONFIG_GTW_ASCII_LOG) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Print message log string into fifo buffer
|
||||
*
|
||||
* This function enables recording of system log messages including CANopen events. Function can be called by
|
||||
* application for recording any message. Message is copied to internal fifo buffer. In case fifo is full, old messages
|
||||
* will be owerwritten. Message log fifo can be read with non-standard command "log". After log is read, it is emptied.
|
||||
* Message must not contain "\r\n" inside. Newline character '\n' will be added between the messages automatically.
|
||||
*
|
||||
* @param gtwa This object
|
||||
* @param message Null terminated string
|
||||
*/
|
||||
void CO_GTWA_log_print(CO_GTWA_t* gtwa, const char* message);
|
||||
#endif /* (CO_CONFIG_GTW) & CO_CONFIG_GTW_ASCII_LOG */
|
||||
|
||||
/**
|
||||
* Process Gateway-ascii object
|
||||
*
|
||||
* This is non-blocking function and must be called cyclically
|
||||
*
|
||||
* @param gtwa This object will be initialized.
|
||||
* @param enable If true, gateway operates normally. If false, gateway is completely disabled and no command interaction
|
||||
* is possible. Can be connected to hardware switch, for example.
|
||||
* @param timeDifference_us Time difference from previous function call in [microseconds].
|
||||
* @param [out] timerNext_us info to OS - see CO_process().
|
||||
*/
|
||||
void CO_GTWA_process(CO_GTWA_t* gtwa, bool_t enable, uint32_t timeDifference_us, uint32_t* timerNext_us);
|
||||
|
||||
/** @} */ /* CO_CANopen_309_3 */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_GTW) & CO_CONFIG_GTW_ASCII */
|
||||
|
||||
#endif /* CO_GATEWAY_ASCII_H */
|
||||
87
Middleware/CANopenNode/402/CiA402_defs.h
Normal file
87
Middleware/CANopenNode/402/CiA402_defs.h
Normal file
@@ -0,0 +1,87 @@
|
||||
#ifndef __CIA402_DEFS_H__
|
||||
#define __CIA402_DEFS_H__
|
||||
|
||||
//本文件存放CiA402中INDEX,controlword,statusword的宏定义替换
|
||||
|
||||
#include "stdint.h"
|
||||
|
||||
/* --- CiA 402 对象字典索引 (Object Dictionary Indices) --- */
|
||||
#define CIA402_INDEX_CONTROLWORD 0x6040 /* 控制字 */
|
||||
#define CIA402_INDEX_STATUSWORD 0x6041 /* 状态字 */
|
||||
#define CIA402_INDEX_OP_MODE 0x6060 /* 目标运动模式 (Modes of Operation) */
|
||||
#define CIA402_INDEX_OP_MODE_DISPLAY 0x6061 /* 模式显示 (Modes of Operation Display) */
|
||||
#define CIA402_INDEX_POS_ACTUAL 0x6064 /* 当前位置值 */
|
||||
#define CIA402_INDEX_FOLLOWING_ERROR_WINDOW 0x6065 /* Following error window */
|
||||
#define CIA402_INDEX_VEL_ACTUAL 0x606C /* 当前速度值 */
|
||||
#define CIA402_INDEX_TARGET_POS 0x607A /* 目标位置 */
|
||||
#define CIA402_INDEX_TARGET_VEL 0x60FF /* 目标速度 */
|
||||
#define CIA402_INDEX_PROFILE_ACC 0x6083 /* 梯形加减速:加速度 */
|
||||
#define CIA402_INDEX_PROFILE_DEC 0x6084 /* 梯形加减速:减速度 */
|
||||
|
||||
/* --- 状态字位定义 (Statusword, 0x6041) --- */
|
||||
#define CIA402_STATUS_READY_TO_SWITCH_ON (1 << 0) /* 准备好切换就绪 */
|
||||
#define CIA402_STATUS_SWITCHED_ON (1 << 1) /* 已切换开启 */
|
||||
#define CIA402_STATUS_OPERATION_ENABLED (1 << 2) /* 运行使能 (动力输出中) */
|
||||
#define CIA402_STATUS_FAULT (1 << 3) /* 故障激活 */
|
||||
#define CIA402_STATUS_VOLTAGE_ENABLED (1 << 4) /* 电压已使能 (主回路通电) */
|
||||
#define CIA402_STATUS_QUICK_STOP (1 << 5) /* 快速停止标志 (0:正在执行快停, 1:正常) */
|
||||
#define CIA402_STATUS_SWITCH_ON_DISABLED (1 << 6) /* 切换开启禁用 */
|
||||
#define CIA402_STATUS_WARNING (1 << 7) /* 警告标志 */
|
||||
#define CIA402_STATUS_REMOTE (1 << 9) /* 远程控制 (可通过网络控制) */
|
||||
#define CIA402_STATUS_TARGET_REACHED (1 << 10) /* 目标位置到达 */
|
||||
#define CIA402_STATUS_INTERNAL_LIMIT_ACTIVE (1 << 11) /* 内部限位激活 */
|
||||
#define CIA402_STATUS_OMS_12 (1 << 12) /* 模式特定位 12 (如 PP模式:Ack, 回零模式:Attained) */
|
||||
#define CIA402_STATUS_OMS_13 (1 << 13) /* 模式特定位 13 (如 PP模式:following error) */
|
||||
|
||||
/* --- 控制字位定义 (Controlword, 0x6040) --- */
|
||||
#define CIA402_CONTROL_SWITCH_ON (1 << 0) /* 切换开启 */
|
||||
#define CIA402_CONTROL_ENABLE_VOLTAGE (1 << 1) /* 使能电压 */
|
||||
#define CIA402_CONTROL_QUICK_STOP (1 << 2) /* 快速停止 (逻辑 0 触发) */
|
||||
#define CIA402_CONTROL_ENABLE_OPERATION (1 << 3) /* 使能运行 */
|
||||
#define CIA402_CONTROL_FAULT_RESET (1 << 7) /* 故障复位 (上升沿触发) */
|
||||
#define CIA402_CONTROL_HALT (1 << 8) /* 暂停运动 */
|
||||
|
||||
/* --- 运动模式特定控制位 (OMS) --- */
|
||||
/* PP 模式 (Profile Position Mode, 模式 1) */
|
||||
#define CIA402_CONTROL_PP_NEW_SET_POINT (1 << 4) /* 新目标点触发 (上升沿触发) */
|
||||
#define CIA402_CONTROL_PP_CHANGE_IMM (1 << 5) /* 立即改变目标 (1:立即更新, 0:完成当前再更新) */
|
||||
#define CIA402_CONTROL_PP_ABS_REL (1 << 6) /* 绝对/相对坐标 (0:绝对, 1:相对) */
|
||||
|
||||
/* 回零模式 (Homing Mode, 模式 6) */
|
||||
#define CIA402_CONTROL_HM_START (1 << 4) /* 启动回零操作 */
|
||||
|
||||
/* --- 常用逻辑掩码与指令组合 --- */
|
||||
/* 运行使能掩码:检查是否处于 Ready + SwOn + OpEn + NoQuickStop 状态 */
|
||||
#define CIA402_STATUS_MASK_OP_ENABLE (0x0027)
|
||||
|
||||
/* 常用指令组合包 */
|
||||
#define CIA402_CMD_SHUTDOWN (CIA402_CONTROL_ENABLE_VOLTAGE | CIA402_CONTROL_QUICK_STOP)
|
||||
#define CIA402_CMD_SWITCH_ON (CIA402_CONTROL_SWITCH_ON | CIA402_CONTROL_ENABLE_VOLTAGE | CIA402_CONTROL_QUICK_STOP)
|
||||
#define CIA402_CMD_ENABLE_OP (CIA402_CMD_SWITCH_ON | CIA402_CONTROL_ENABLE_VOLTAGE | \
|
||||
CIA402_CONTROL_QUICK_STOP | CIA402_CONTROL_ENABLE_OPERATION)
|
||||
|
||||
// CiA402 状态机状态 (标准定义)
|
||||
typedef enum
|
||||
{
|
||||
STATE_NOT_READY_TO_SWITCH_ON,
|
||||
STATE_SWITCH_ON_DISABLED,
|
||||
STATE_READY_TO_SWITCH_ON,
|
||||
STATE_SWITCHED_ON,
|
||||
STATE_OPERATION_ENABLED,
|
||||
STATE_FAULT,
|
||||
STATE_QUICK_STOP_ACTIVE
|
||||
} Motor_State_t;
|
||||
|
||||
|
||||
|
||||
// 回零专用状态机
|
||||
typedef enum
|
||||
{
|
||||
HOMING_IDLE, // 空闲
|
||||
HOMING_START, // 准备启动
|
||||
HOMING_MOVING, // 正在找开关
|
||||
HOMING_DONE // 完成
|
||||
} Homing_State_t;
|
||||
|
||||
#endif
|
||||
|
||||
1632
Middleware/CANopenNode/CANopen.c
Normal file
1632
Middleware/CANopenNode/CANopen.c
Normal file
File diff suppressed because it is too large
Load Diff
588
Middleware/CANopenNode/CANopen.h
Normal file
588
Middleware/CANopenNode/CANopen.h
Normal file
@@ -0,0 +1,588 @@
|
||||
/**
|
||||
* Main CANopenNode file.
|
||||
*
|
||||
* @file CANopen.h
|
||||
* @ingroup CO_CANopen
|
||||
* @author Janez Paternoster
|
||||
* @author Uwe Kindler
|
||||
* @copyright 2010 - 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 CANopen_H
|
||||
#define CANopen_H
|
||||
|
||||
#include "301/CO_driver.h"
|
||||
#include "301/CO_ODinterface.h"
|
||||
#include "301/CO_NMT_Heartbeat.h"
|
||||
#include "301/CO_HBconsumer.h"
|
||||
#include "301/CO_Node_Guarding.h"
|
||||
#include "301/CO_Emergency.h"
|
||||
#include "301/CO_SDOserver.h"
|
||||
#include "301/CO_SDOclient.h"
|
||||
#include "301/CO_SYNC.h"
|
||||
#include "301/CO_PDO.h"
|
||||
#include "301/CO_TIME.h"
|
||||
#include "303/CO_LEDs.h"
|
||||
#include "304/CO_GFC.h"
|
||||
#include "304/CO_SRDO.h"
|
||||
#include "305/CO_LSSslave.h"
|
||||
#include "305/CO_LSSmaster.h"
|
||||
#include "309/CO_gateway_ascii.h"
|
||||
#include "extra/CO_trace.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_CANopen CANopen
|
||||
* @{
|
||||
*
|
||||
* CANopenNode is free and open source CANopen communication protocol stack.
|
||||
*
|
||||
* CANopen is the internationally standardized (EN 50325-4) (CiA DS-301) CAN-based higher-layer protocol for embedded
|
||||
* control system. For more information on CANopen see http://www.can-cia.org/
|
||||
*
|
||||
* CANopenNode homepage is https://github.com/CANopenNode/CANopenNode
|
||||
*
|
||||
* CANopen.h file combines all CANopenNode source files. @ref CO_STACK_CONFIG is first defined in "CO_config.h" file.
|
||||
* Number of different CANopenNode objects used is configured with @ref CO_config_t structure or is read directly from
|
||||
* "OD.h" file, if single object dictionary definition is used. "OD.h" and "OD.c" files defines CANopen Object
|
||||
* Dictionary and are generated by external tool.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup CO_CANopen_301 CANopen_301
|
||||
* @{
|
||||
*
|
||||
* CANopen application layer and communication profile (CiA 301 v4.2.0)
|
||||
*
|
||||
* Definitions of data types, encoding rules, object dictionary objects and CANopen communication services and
|
||||
* protocols.
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup CO_CANopen_303 CANopen_303
|
||||
* @{
|
||||
*
|
||||
* CANopen recommendation for indicator specification (CiA 303-3 v1.4.0)
|
||||
*
|
||||
* Description of communication related indicators - green and red LED diodes.
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup CO_CANopen_304 CANopen_304
|
||||
* @{
|
||||
*
|
||||
* CANopen Safety (EN 50325-5:2010 (formerly CiA 304))
|
||||
*
|
||||
* Standard defines the usage of Safety Related Data Objects (SRDO) and the GFC. This is an additional protocol (to SDO,
|
||||
* PDO) to exchange data. The meaning of "security" here refers not to security (crypto) but to data consistency.
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup CO_CANopen_305 CANopen_305
|
||||
* @{
|
||||
*
|
||||
* CANopen layer setting services (LSS) and protocols (CiA 305 DSP v3.0.0)
|
||||
*
|
||||
* Inquire or change three parameters on a CANopen device with LSS slave capability by a CANopen device with LSS master
|
||||
* capability via the CAN network: the settings of Node-ID of the CANopen device, bit timing parameters of the physical
|
||||
* layer (bit rate) or LSS address compliant to the identity object (1018h).
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup CO_CANopen_309 CANopen_309
|
||||
* @{
|
||||
*
|
||||
* CANopen access from other networks (CiA 309)
|
||||
*
|
||||
* Standard defines the services and protocols to interface CANopen networks to other networks. Standard is organized as
|
||||
* follows:
|
||||
* - Part 1: General principles and services
|
||||
* - Part 2: Modbus/TCP mapping
|
||||
* - Part 3: ASCII mapping
|
||||
* - Part 4: Amendment 7 to Fieldbus Integration into PROFINET IO
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup CO_CANopen_storage CANopen_storage
|
||||
* @{
|
||||
*
|
||||
* CANopen Object Dictionary and other data storage.
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup CO_CANopen_extra CANopen_extra
|
||||
* @{
|
||||
*
|
||||
* Additional non-standard objects related to CANopenNode.
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup CO_CANopen
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* If macro is defined externally, then configuration with multiple object dictionaries will be possible. If macro is
|
||||
* not defined, default "OD.h" file with necessary definitions, such as OD_CNT_xxx, will be used, and also memory
|
||||
* consumption and startup time will be lower.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_MULTIPLE_OD
|
||||
#endif
|
||||
|
||||
/**
|
||||
* If macro is defined externally, then global variables for CANopen objects
|
||||
* will be used instead of heap. This is possible only if CO_MULTIPLE_OD is not
|
||||
* defined.
|
||||
*/
|
||||
#ifdef CO_DOXYGEN
|
||||
#define CO_USE_GLOBALS
|
||||
#endif
|
||||
|
||||
#if defined CO_MULTIPLE_OD || defined CO_DOXYGEN
|
||||
/**
|
||||
* CANopen configuration, used with @ref CO_new()
|
||||
*
|
||||
* This structure is used only, if @ref CO_MULTIPLE_OD is enabled. Otherwise parameters are retrieved from default
|
||||
* "OD.h" file.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t CNT_NMT; /**< Number of NMT objects, 0 or 1: NMT slave (CANrx) + Heartbeat producer (CANtx) +
|
||||
optional NMT master (CANtx), configurable by @ref CO_CONFIG_NMT. Start indexes inside
|
||||
CANrx and CANtx are always 0. There must be one NMT object in the device. */
|
||||
OD_entry_t* ENTRY_H1017; /**< OD entry for @ref CO_NMT_init() */
|
||||
uint8_t CNT_HB_CONS; /**< Number of Heartbeat consumer objects, 0 or 1 */
|
||||
uint8_t CNT_ARR_1016; /**< Number of internal consumers (CANrx), used inside Heartbeat consumer object, 1 to 127. */
|
||||
OD_entry_t* ENTRY_H1016; /**< OD entry for @ref CO_HBconsumer_init() */
|
||||
OD_entry_t* ENTRY_H100C; /**< OD entry for @ref CO_nodeGuardingSlave_init() */
|
||||
OD_entry_t* ENTRY_H100D; /**< OD entry for @ref CO_nodeGuardingSlave_init() */
|
||||
uint8_t CNT_EM; /**< Number of Emergency objects, 0 or 1: optional producer (CANtx) + optional consumer (CANrx),
|
||||
configurable by @ref CO_CONFIG_EM. There must be one Emergency object in the device. */
|
||||
const OD_entry_t* ENTRY_H1001; /**< OD entry for @ref CO_EM_init() */
|
||||
OD_entry_t* ENTRY_H1014; /**< OD entry for @ref CO_EM_init() */
|
||||
OD_entry_t* ENTRY_H1015; /**< OD entry for @ref CO_EM_init() */
|
||||
uint8_t CNT_ARR_1003; /**< Size of the fifo buffer, which is used for intermediate storage of emergency messages.
|
||||
Fifo is used by emergency producer and by error history (OD object 0x1003). Size is usually
|
||||
equal to size of array in OD object 0x1003. If later is not used, CNT_ARR_1003 must also be
|
||||
set to value greater than 0, or emergency producer will not work. */
|
||||
OD_entry_t* ENTRY_H1003; /**< OD entry for @ref CO_EM_init() */
|
||||
uint8_t CNT_SDO_SRV; /**< Number of SDO server objects, from 0 to 128 (CANrx + CANtx). There must be at least
|
||||
one SDO server object in the device. */
|
||||
OD_entry_t* ENTRY_H1200; /**< OD entry for @ref CO_SDOserver_init() */
|
||||
uint8_t CNT_SDO_CLI; /**< Number of SDO client objects, from 0 to 128 (CANrx + CANtx). */
|
||||
OD_entry_t* ENTRY_H1280; /**< OD entry for @ref CO_SDOclient_init() */
|
||||
uint8_t CNT_TIME; /**< Number of TIME objects, 0 or 1: consumer (CANrx) + optional producer (CANtx),
|
||||
configurable by @ref CO_CONFIG_TIME. */
|
||||
OD_entry_t* ENTRY_H1012; /**< OD entry for @ref CO_TIME_init() */
|
||||
uint8_t CNT_SYNC; /**< Number of SYNC objects, 0 or 1: consumer (CANrx) + optional producer (CANtx),
|
||||
configurable by @ref CO_CONFIG_SYNC. */
|
||||
OD_entry_t* ENTRY_H1005; /**< OD entry for @ref CO_SYNC_init() */
|
||||
OD_entry_t* ENTRY_H1006; /**< OD entry for @ref CO_SYNC_init() */
|
||||
OD_entry_t* ENTRY_H1007; /**< OD entry for @ref CO_SYNC_init() */
|
||||
OD_entry_t* ENTRY_H1019; /**< OD entry for @ref CO_SYNC_init() */
|
||||
uint16_t CNT_RPDO; /**< Number of RPDO objects, from 0 to 512 consumers (CANrx) */
|
||||
OD_entry_t* ENTRY_H1400; /**< OD entry for @ref CO_RPDO_init() */
|
||||
OD_entry_t* ENTRY_H1600; /**< OD entry for @ref CO_RPDO_init() */
|
||||
uint16_t CNT_TPDO; /**< Number of TPDO objects, from 0 to 512 producers (CANtx) */
|
||||
OD_entry_t* ENTRY_H1800; /**< OD entry for @ref CO_TPDO_init() */
|
||||
OD_entry_t* ENTRY_H1A00; /**< OD entry for @ref CO_TPDO_init() */
|
||||
uint8_t CNT_LEDS; /**< Number of LEDs objects, 0 or 1. */
|
||||
uint8_t CNT_GFC; /**< Number of GFC objects, 0 or 1 (CANrx + CANtx). */
|
||||
OD_entry_t* ENTRY_H1300; /**< OD entry for @ref CO_GFC_init() */
|
||||
uint8_t CNT_SRDO; /**< Number of SRDO objects, from 0 to 64 (2*CANrx + 2*CANtx). */
|
||||
OD_entry_t* ENTRY_H1301; /**< OD entry for @ref CO_SRDO_init() */
|
||||
OD_entry_t* ENTRY_H1381; /**< OD entry for @ref CO_SRDO_init() */
|
||||
OD_entry_t* ENTRY_H13FE; /**< OD entry for @ref CO_SRDO_init() */
|
||||
OD_entry_t* ENTRY_H13FF; /**< OD entry for @ref CO_SRDO_init() */
|
||||
uint8_t CNT_LSS_SLV; /**< Number of LSSslave objects, 0 or 1 (CANrx + CANtx). */
|
||||
uint8_t CNT_LSS_MST; /**< Number of LSSmaster objects, 0 or 1 (CANrx + CANtx). */
|
||||
uint8_t CNT_GTWA; /**< Number of gateway ascii objects, 0 or 1. */
|
||||
uint16_t CNT_TRACE; /**< Number of trace objects, 0 or more. */
|
||||
} CO_config_t;
|
||||
#else
|
||||
typedef void CO_config_t;
|
||||
#endif /* CO_MULTIPLE_OD */
|
||||
|
||||
/**
|
||||
* CANopen object - collection of all CANopenNode objects
|
||||
*/
|
||||
typedef struct {
|
||||
bool_t nodeIdUnconfigured; /**< True in un-configured LSS slave */
|
||||
#if defined CO_MULTIPLE_OD || defined CO_DOXYGEN
|
||||
CO_config_t* config; /**< Remember the configuration parameters */
|
||||
#endif
|
||||
CO_CANmodule_t* CANmodule; /**< One CAN module object, initialised by @ref CO_CANmodule_init() */
|
||||
CO_CANrx_t* CANrx; /**< CAN receive message objects */
|
||||
CO_CANtx_t* CANtx; /**< CAN transmit message objects */
|
||||
#if defined CO_MULTIPLE_OD || defined CO_DOXYGEN
|
||||
uint16_t CNT_ALL_RX_MSGS; /**< Number of all CAN receive message objects. */
|
||||
uint16_t CNT_ALL_TX_MSGS; /**< Number of all CAN transmit message objects. */
|
||||
#endif
|
||||
CO_NMT_t* NMT; /**< NMT and heartbeat object, initialised by @ref CO_NMT_init() */
|
||||
#if defined CO_MULTIPLE_OD || defined CO_DOXYGEN
|
||||
uint16_t RX_IDX_NMT_SLV; /**< Start index in CANrx. */
|
||||
uint16_t TX_IDX_NMT_MST; /**< Start index in CANtx. */
|
||||
uint16_t TX_IDX_HB_PROD; /**< Start index in CANtx. */
|
||||
#endif
|
||||
#if (((CO_CONFIG_HB_CONS)&CO_CONFIG_HB_CONS_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
CO_HBconsumer_t* HBcons; /**< Heartbeat consumer object, initialised by @ref CO_HBconsumer_init() */
|
||||
CO_HBconsNode_t* HBconsMonitoredNodes; /**< Object for monitored nodes, initialised by @ref CO_HBconsumer_init() */
|
||||
#if defined CO_MULTIPLE_OD || defined CO_DOXYGEN
|
||||
uint16_t RX_IDX_HB_CONS; /**< Start index in CANrx. */
|
||||
#endif
|
||||
#endif
|
||||
#if (((CO_CONFIG_NODE_GUARDING)&CO_CONFIG_NODE_GUARDING_SLAVE_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
CO_nodeGuardingSlave_t* NGslave; /**< Node guarding slave object, initialised by @ref CO_nodeGuardingSlave_init() */
|
||||
#if defined CO_MULTIPLE_OD || defined CO_DOXYGEN
|
||||
uint16_t RX_IDX_NG_SLV; /**< Start index in CANrx. */
|
||||
uint16_t TX_IDX_NG_SLV; /**< Start index in CANtx. */
|
||||
#endif
|
||||
#endif
|
||||
#if (((CO_CONFIG_NODE_GUARDING)&CO_CONFIG_NODE_GUARDING_MASTER_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
CO_nodeGuardingMaster_t*
|
||||
NGmaster; /**< Node guarding master object, initialised by @ref CO_nodeGuardingMaster_init() */
|
||||
#if defined CO_MULTIPLE_OD || defined CO_DOXYGEN
|
||||
uint16_t RX_IDX_NG_MST; /**< Start index in CANrx. */
|
||||
uint16_t TX_IDX_NG_MST; /**< Start index in CANtx. */
|
||||
#endif
|
||||
#endif
|
||||
CO_EM_t* em; /**< Emergency object, initialised by @ref CO_EM_init() */
|
||||
#if defined CO_MULTIPLE_OD || defined CO_DOXYGEN
|
||||
uint16_t RX_IDX_EM_CONS; /**< Start index in CANrx. */
|
||||
uint16_t TX_IDX_EM_PROD; /**< Start index in CANtx. */
|
||||
#endif
|
||||
#if (((CO_CONFIG_EM) & (CO_CONFIG_EM_PRODUCER | CO_CONFIG_EM_HISTORY)) != 0) || defined CO_DOXYGEN
|
||||
CO_EM_fifo_t* em_fifo; /**< FIFO for emergency object, initialised by @ref CO_EM_init() */
|
||||
#endif
|
||||
CO_SDOserver_t* SDOserver; /**< SDO server objects, initialised by @ref CO_SDOserver_init() */
|
||||
#if defined CO_MULTIPLE_OD || defined CO_DOXYGEN
|
||||
uint16_t RX_IDX_SDO_SRV; /**< Start index in CANrx. */
|
||||
uint16_t TX_IDX_SDO_SRV; /**< Start index in CANtx. */
|
||||
#endif
|
||||
#if (((CO_CONFIG_SDO_CLI)&CO_CONFIG_SDO_CLI_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
CO_SDOclient_t* SDOclient; /**< SDO client objects, initialised by @ref CO_SDOclient_init() */
|
||||
#if defined CO_MULTIPLE_OD || defined CO_DOXYGEN
|
||||
uint16_t RX_IDX_SDO_CLI; /**< Start index in CANrx. */
|
||||
uint16_t TX_IDX_SDO_CLI; /**< Start index in CANtx. */
|
||||
#endif
|
||||
#endif
|
||||
#if (((CO_CONFIG_TIME)&CO_CONFIG_TIME_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
CO_TIME_t* TIME; /**< TIME object, initialised by @ref CO_TIME_init() */
|
||||
#if defined CO_MULTIPLE_OD || defined CO_DOXYGEN
|
||||
uint16_t RX_IDX_TIME; /**< Start index in CANrx. */
|
||||
uint16_t TX_IDX_TIME; /**< Start index in CANtx. */
|
||||
#endif
|
||||
#endif
|
||||
#if (((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
CO_SYNC_t* SYNC; /**< SYNC object, initialised by @ref CO_SYNC_init() */
|
||||
#if defined CO_MULTIPLE_OD || defined CO_DOXYGEN
|
||||
uint16_t RX_IDX_SYNC; /**< Start index in CANrx. */
|
||||
uint16_t TX_IDX_SYNC; /**< Start index in CANtx. */
|
||||
#endif
|
||||
#endif
|
||||
#if (((CO_CONFIG_PDO)&CO_CONFIG_RPDO_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
CO_RPDO_t* RPDO; /**< RPDO objects, initialised by @ref CO_RPDO_init() */
|
||||
#if defined CO_MULTIPLE_OD || defined CO_DOXYGEN
|
||||
uint16_t RX_IDX_RPDO; /**< Start index in CANrx. */
|
||||
#endif
|
||||
#endif
|
||||
#if (((CO_CONFIG_PDO)&CO_CONFIG_TPDO_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
CO_TPDO_t* TPDO; /**< TPDO objects, initialised by @ref CO_TPDO_init() */
|
||||
#if defined CO_MULTIPLE_OD || defined CO_DOXYGEN
|
||||
uint16_t TX_IDX_TPDO; /**< Start index in CANtx. */
|
||||
#endif
|
||||
#endif
|
||||
#if (((CO_CONFIG_LEDS)&CO_CONFIG_LEDS_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
CO_LEDs_t* LEDs; /**< LEDs object, initialised by @ref CO_LEDs_init() */
|
||||
#endif
|
||||
#if (((CO_CONFIG_GFC)&CO_CONFIG_GFC_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
CO_GFC_t* GFC; /**< GFC object, initialised by @ref CO_GFC_init() */
|
||||
#if defined CO_MULTIPLE_OD || defined CO_DOXYGEN
|
||||
uint16_t RX_IDX_GFC; /**< Start index in CANrx. */
|
||||
uint16_t TX_IDX_GFC; /**< Start index in CANtx. */
|
||||
#endif
|
||||
#endif
|
||||
#if (((CO_CONFIG_SRDO)&CO_CONFIG_SRDO_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
CO_SRDOGuard_t* SRDOGuard; /**< SRDO guard object, initialised by CO_SRDOGuard_init(), single SRDOGuard object is
|
||||
included inside all SRDO objects */
|
||||
CO_SRDO_t* SRDO; /**< SRDO objects, initialised by @ref CO_SRDO_init() */
|
||||
#if defined CO_MULTIPLE_OD || defined CO_DOXYGEN
|
||||
uint16_t RX_IDX_SRDO; /**< Start index in CANrx. */
|
||||
uint16_t TX_IDX_SRDO; /**< Start index in CANtx. */
|
||||
#endif
|
||||
#endif
|
||||
#if (((CO_CONFIG_LSS)&CO_CONFIG_LSS_SLAVE) != 0) || defined CO_DOXYGEN
|
||||
CO_LSSslave_t* LSSslave; /**< LSS slave object, initialised by @ref CO_LSSslave_init(). */
|
||||
#if defined CO_MULTIPLE_OD || defined CO_DOXYGEN
|
||||
uint16_t RX_IDX_LSS_SLV; /**< Start index in CANrx. */
|
||||
uint16_t TX_IDX_LSS_SLV; /**< Start index in CANtx. */
|
||||
#endif
|
||||
#endif
|
||||
#if (((CO_CONFIG_LSS)&CO_CONFIG_LSS_MASTER) != 0) || defined CO_DOXYGEN
|
||||
CO_LSSmaster_t* LSSmaster; /**< LSS master object, initialised by @ref CO_LSSmaster_init(). */
|
||||
#if defined CO_MULTIPLE_OD || defined CO_DOXYGEN
|
||||
uint16_t RX_IDX_LSS_MST; /**< Start index in CANrx. */
|
||||
uint16_t TX_IDX_LSS_MST; /**< Start index in CANtx. */
|
||||
#endif
|
||||
#endif
|
||||
#if (((CO_CONFIG_GTW)&CO_CONFIG_GTW_ASCII) != 0) || defined CO_DOXYGEN
|
||||
CO_GTWA_t* gtwa; /**< Gateway-ascii object, initialised by @ref CO_GTWA_init(). */
|
||||
#if defined CO_MULTIPLE_OD || defined CO_DOXYGEN
|
||||
#endif
|
||||
#endif
|
||||
#if ((CO_CONFIG_TRACE)&CO_CONFIG_TRACE_ENABLE) || defined CO_DOXYGEN
|
||||
CO_trace_t* trace; /**< Trace object, initialised by @ref CO_trace_init(). */
|
||||
#endif
|
||||
} CO_t;
|
||||
|
||||
/**
|
||||
* Create new CANopen object
|
||||
*
|
||||
* If CO_USE_GLOBALS is defined, then function uses global static variables for all the CANopenNode objects. Otherwise
|
||||
* it allocates all objects from heap.
|
||||
*
|
||||
* @remark
|
||||
* With some microcontrollers it is necessary to specify Heap size within linker configuration, if heap is used.
|
||||
*
|
||||
* @param config Configuration structure, used if @ref CO_MULTIPLE_OD is defined. It must stay in memory permanently. If
|
||||
* CO_MULTIPLE_OD is not defined, config should be NULL and parameters are retrieved from default "OD.h" file.
|
||||
* @param [out] heapMemoryUsed Information about heap memory used. Ignored if NULL.
|
||||
*
|
||||
* @return Successfully allocated and configured CO_t object or NULL.
|
||||
*/
|
||||
CO_t* CO_new(CO_config_t* config, uint32_t* heapMemoryUsed);
|
||||
|
||||
/**
|
||||
* Delete CANopen object and free memory. Must be called at program exit.
|
||||
*
|
||||
* @param co CANopen object.
|
||||
*/
|
||||
void CO_delete(CO_t* co);
|
||||
|
||||
/**
|
||||
* Test if LSS slave is enabled
|
||||
*
|
||||
* @param co CANopen object.
|
||||
*
|
||||
* @return True if enabled
|
||||
*/
|
||||
bool_t CO_isLSSslaveEnabled(CO_t* co);
|
||||
|
||||
/**
|
||||
* Initialize CAN driver
|
||||
*
|
||||
* Function must be called in the communication reset section.
|
||||
*
|
||||
* @param co CANopen object.
|
||||
* @param CANptr Pointer to the user-defined CAN base structure, passed to CO_CANmodule_init().
|
||||
* @param bitRate CAN bit rate.
|
||||
* @return CO_ERROR_NO in case of success.
|
||||
*/
|
||||
CO_ReturnError_t CO_CANinit(CO_t* co, void* CANptr, uint16_t bitRate);
|
||||
|
||||
#if (((CO_CONFIG_LSS)&CO_CONFIG_LSS_SLAVE) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Initialize CANopen LSS slave
|
||||
*
|
||||
* Function must be called before CO_CANopenInit.
|
||||
*
|
||||
* See @ref CO_LSSslave_init() for description of parameters.
|
||||
*
|
||||
* @param co CANopen object.
|
||||
* @param lssAddress LSS slave address, from OD object 0x1018
|
||||
* @param [in,out] pendingNodeID Pending node ID or 0xFF (unconfigured)
|
||||
* @param [in,out] pendingBitRate Pending bit rate of the CAN interface
|
||||
*
|
||||
* @return CO_ERROR_NO in case of success.
|
||||
*/
|
||||
CO_ReturnError_t CO_LSSinit(CO_t* co, CO_LSS_address_t* lssAddress, uint8_t* pendingNodeID, uint16_t* pendingBitRate);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initialize CANopenNode except PDO objects.
|
||||
*
|
||||
* Function must be called in the communication reset section.
|
||||
*
|
||||
* @param co CANopen object.
|
||||
* @param em Emergency object, which is used inside different CANopen objects, usually for error reporting. If NULL,
|
||||
* then 'co->em' will be used. if NULL and 'co->CNT_EM' is 0, then function returns with error.
|
||||
* @param NMT If 'co->CNT_NMT' is 0, this object must be specified, If 'co->CNT_NMT' is 1,then it is ignored and can be
|
||||
* NULL. NMT object is used for retrieving NMT internal state inside CO_process().
|
||||
* @param od CANopen Object dictionary
|
||||
* @param OD_statusBits Argument passed to @ref CO_EM_init(). May be NULL.
|
||||
* @param NMTcontrol Argument passed to @ref CO_NMT_init().
|
||||
* @param firstHBTime_ms Argument passed to @ref CO_NMT_init().
|
||||
* @param SDOserverTimeoutTime_ms Argument passed to @ref CO_SDOserver_init().
|
||||
* @param SDOclientTimeoutTime_ms Default timeout in milliseconds for SDO client, 500 typically. SDO client is
|
||||
* configured from CO_GTWA_init().
|
||||
* @param SDOclientBlockTransfer If true, block transfer will be set in SDO client by default. SDO client is configured
|
||||
* from by CO_GTWA_init().
|
||||
* @param nodeId CANopen Node ID (1 ... 127) or 0xFF(unconfigured). In the CANopen initialization it is the same as
|
||||
* pendingBitRate from CO_LSSinit(). If it is unconfigured, then some CANopen objects will not be initialized nor
|
||||
* processed.
|
||||
* @param [out] errInfo Additional information in case of error, may be NULL. errInfo can also be set in noncritical
|
||||
* errors, where function returns CO_ERROR_NO. For example, if OD parameter contains wrong value.
|
||||
*
|
||||
* @return CO_ERROR_NO in case of success.
|
||||
*/
|
||||
CO_ReturnError_t CO_CANopenInit(CO_t* co, CO_NMT_t* NMT, CO_EM_t* em, OD_t* od, OD_entry_t* OD_statusBits,
|
||||
uint16_t NMTcontrol, uint16_t firstHBTime_ms, uint16_t SDOserverTimeoutTime_ms,
|
||||
uint16_t SDOclientTimeoutTime_ms, bool_t SDOclientBlockTransfer, uint8_t nodeId,
|
||||
uint32_t* errInfo);
|
||||
|
||||
/**
|
||||
* Initialize CANopenNode PDO objects.
|
||||
*
|
||||
* Function must be called in the end of communication reset section after all CANopen and application initialization,
|
||||
* otherwise some OD variables wont be mapped into PDO correctly.
|
||||
*
|
||||
* @param co CANopen object.
|
||||
* @param em Emergency object, which is used inside PDO objects for error reporting.
|
||||
* @param od CANopen Object dictionary
|
||||
* @param nodeId CANopen Node ID (1 ... 127) or 0xFF(unconfigured). If unconfigured, then PDO will not be initialized
|
||||
* nor processed.
|
||||
* @param [out] errInfo Additional information in case of error, may be NULL.
|
||||
*
|
||||
* @return CO_ERROR_NO in case of success.
|
||||
*/
|
||||
CO_ReturnError_t CO_CANopenInitPDO(CO_t* co, CO_EM_t* em, OD_t* od, uint8_t nodeId, uint32_t* errInfo);
|
||||
|
||||
/**
|
||||
* Initialize Safety related Data Objects.
|
||||
*
|
||||
* Function must be called in the end of communication reset section after all CANopen and application initialization,
|
||||
* otherwise some OD variables wont be mapped into SRDO correctly.
|
||||
*
|
||||
* @param co CANopen object.
|
||||
* @param em Emergency object, which is used inside PDO objects for error reporting.
|
||||
* @param od CANopen Object dictionary
|
||||
* @param nodeId CANopen Node ID (1 ... 127) or 0xFF(unconfigured). If unconfigured, then PDO will not be initialized
|
||||
* nor processed.
|
||||
* @param [out] errInfo Additional information in case of error, may be NULL.
|
||||
*
|
||||
* @return #CO_ERROR_NO in case of success.
|
||||
*/
|
||||
#if (((CO_CONFIG_GFC)&CO_CONFIG_GFC_ENABLE) != 0) || (((CO_CONFIG_SRDO)&CO_CONFIG_SRDO_ENABLE) != 0) \
|
||||
|| defined CO_DOXYGEN
|
||||
CO_ReturnError_t CO_CANopenInitSRDO(CO_t* co, CO_EM_t* em, OD_t* od, uint8_t nodeId, uint32_t* errInfo);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Process CANopen objects.
|
||||
*
|
||||
* Function must be called cyclically. It processes all "asynchronous" CANopen objects.
|
||||
*
|
||||
* @param co CANopen object.
|
||||
* @param enableGateway If true, gateway to external world will be enabled.
|
||||
* @param timeDifference_us Time difference from previous function call in microseconds.
|
||||
* @param [out] timerNext_us info to OS - maximum delay time after this function should be called next time in
|
||||
* [microseconds]. Value can be used for OS sleep time. Initial value must be set to maximum interval time. Output will
|
||||
* be equal or lower to initial value. Calculation is based on various timers which expire in known time. Parameter
|
||||
* should be used in combination with callbacks configured with CO_***_initCallbackPre() functions. Those callbacks
|
||||
* should also trigger calling of CO_process() function. Parameter is ignored if NULL. See also @ref
|
||||
* CO_CONFIG_FLAG_CALLBACK_PRE configuration macro.
|
||||
*
|
||||
* @return Node or communication reset request, from @ref CO_NMT_process().
|
||||
*/
|
||||
CO_NMT_reset_cmd_t CO_process(CO_t* co, bool_t enableGateway, uint32_t timeDifference_us, uint32_t* timerNext_us);
|
||||
|
||||
#if (((CO_CONFIG_SYNC)&CO_CONFIG_SYNC_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Process CANopen SYNC objects.
|
||||
*
|
||||
* Function must be called cyclically. For time critical applications it may be called from real time thread with
|
||||
* constant interval (1ms typically). It processes SYNC CANopen objects.
|
||||
*
|
||||
* @param co CANopen object.
|
||||
* @param timeDifference_us Time difference from previous function call in microseconds.
|
||||
* @param [out] timerNext_us info to OS - see CO_process().
|
||||
*
|
||||
* @return True, if CANopen SYNC message was just received or transmitted.
|
||||
*/
|
||||
bool_t CO_process_SYNC(CO_t* co, uint32_t timeDifference_us, uint32_t* timerNext_us);
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_PDO)&CO_CONFIG_RPDO_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Process CANopen RPDO objects.
|
||||
*
|
||||
* Function must be called cyclically. For time critical applications it may be called from real time thread with
|
||||
* constant interval (1ms typically). It processes receive PDO CANopen objects.
|
||||
*
|
||||
* @param co CANopen object.
|
||||
* @param syncWas True, if CANopen SYNC message was just received or transmitted.
|
||||
* @param timeDifference_us Time difference from previous function call in microseconds.
|
||||
* @param [out] timerNext_us info to OS - see CO_process().
|
||||
*/
|
||||
void CO_process_RPDO(CO_t* co, bool_t syncWas, uint32_t timeDifference_us, uint32_t* timerNext_us);
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_PDO)&CO_CONFIG_TPDO_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Process CANopen TPDO objects.
|
||||
*
|
||||
* Function must be called cyclically. For time critical applications it may be called from real time thread with
|
||||
* constant interval (1ms typically). It processes transmit PDO CANopen objects.
|
||||
*
|
||||
* @param co CANopen object.
|
||||
* @param syncWas True, if CANopen SYNC message was just received or transmitted.
|
||||
* @param timeDifference_us Time difference from previous function call in microseconds.
|
||||
* @param [out] timerNext_us info to OS - see CO_process().
|
||||
*/
|
||||
void CO_process_TPDO(CO_t* co, bool_t syncWas, uint32_t timeDifference_us, uint32_t* timerNext_us);
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_SRDO)&CO_CONFIG_SRDO_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
/**
|
||||
* Process CANopen SRDO objects.
|
||||
*
|
||||
* Function must be called cyclically. For time critical applications it may be called from real time thread with
|
||||
* constant interval (1ms typically). It processes SRDO CANopen objects.
|
||||
*
|
||||
* @param co CANopen object.
|
||||
* @param timeDifference_us Time difference from previous function call in microseconds.
|
||||
* @param [out] timerNext_us info to OS - see CO_process().
|
||||
*
|
||||
* @return #CO_SRDO_state_t: lowest state of the SRDO objects.
|
||||
*/
|
||||
CO_SRDO_state_t CO_process_SRDO(CO_t* co, uint32_t timeDifference_us, uint32_t* timerNext_us);
|
||||
#endif
|
||||
|
||||
/** @} */ /* CO_CANopen */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CANopen_H */
|
||||
274
Middleware/CANopenNode/CO_driver_STM32/CO_app_STM32.c
Normal file
274
Middleware/CANopenNode/CO_driver_STM32/CO_app_STM32.c
Normal file
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* CANopen main program file.
|
||||
*
|
||||
* This file is a template for other microcontrollers.
|
||||
*
|
||||
* @file main_generic.c
|
||||
* @author Hamed Jafarzadeh 2022
|
||||
* Janez Paternoster 2021
|
||||
* @copyright 2021 Janez Paternoster
|
||||
*
|
||||
* This file is part of CANopenNode, an opensource CANopen Stack.
|
||||
* Project home page is <https://github.com/CANopenNode/CANopenNode>.
|
||||
* For more information on CANopen see <http://www.can-cia.org/>.
|
||||
*
|
||||
* 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 "CO_app_STM32.h"
|
||||
#include "CANopen.h"
|
||||
#include "main.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#include "CO_storageBlank.h"
|
||||
#include "OD.h"
|
||||
|
||||
CANopenNodeSTM32 *
|
||||
canopenNodeSTM32; // It will be set by canopen_app_init and will be used across app to get access to CANOpen objects
|
||||
|
||||
/* Printf function of CanOpen app */
|
||||
#define log_printf(macropar_message, ...) printf(macropar_message, ##__VA_ARGS__)
|
||||
|
||||
/* default values for CO_CANopenInit() */
|
||||
#define NMT_CONTROL \
|
||||
CO_NMT_STARTUP_TO_OPERATIONAL \
|
||||
| CO_NMT_ERR_ON_ERR_REG | CO_ERR_REG_GENERIC_ERR | CO_ERR_REG_COMMUNICATION
|
||||
#define FIRST_HB_TIME 500
|
||||
#define SDO_SRV_TIMEOUT_TIME 1000
|
||||
#define SDO_CLI_TIMEOUT_TIME 500
|
||||
#define SDO_CLI_BLOCK false
|
||||
#define OD_STATUS_BITS NULL
|
||||
|
||||
/* Global variables and objects */
|
||||
CO_t *CO = NULL; /* CANopen object */
|
||||
|
||||
// Global variables
|
||||
uint32_t time_old, time_current;
|
||||
CO_ReturnError_t err;
|
||||
|
||||
/* This function will basically setup the CANopen node */
|
||||
int canopen_app_init(CANopenNodeSTM32 *_canopenNodeSTM32)
|
||||
{
|
||||
|
||||
// Keep a copy global reference of canOpenSTM32 Object
|
||||
canopenNodeSTM32 = _canopenNodeSTM32;
|
||||
|
||||
#if (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE
|
||||
static CO_storage_t storage;
|
||||
static CO_storage_entry_t storageEntries[] = {{.addr = &OD_PERSIST_COMM,
|
||||
.len = sizeof(OD_PERSIST_COMM),
|
||||
.subIndexOD = 2,
|
||||
.attr = CO_storage_cmd | CO_storage_restore,
|
||||
.addrNV = NULL}};
|
||||
uint8_t storageEntriesCount = sizeof(storageEntries) / sizeof(storageEntries[0]);
|
||||
uint32_t storageInitError = 0;
|
||||
#endif
|
||||
|
||||
/* Allocate memory */
|
||||
CO_config_t *config_ptr = NULL;
|
||||
#ifdef CO_MULTIPLE_OD
|
||||
/* example usage of CO_MULTIPLE_OD (but still single OD here) */
|
||||
CO_config_t co_config = {0};
|
||||
OD_INIT_CONFIG(co_config); /* helper macro from OD.h */
|
||||
co_config.CNT_LEDS = 1;
|
||||
co_config.CNT_LSS_SLV = 1;
|
||||
config_ptr = &co_config;
|
||||
#endif /* CO_MULTIPLE_OD */
|
||||
|
||||
uint32_t heapMemoryUsed;
|
||||
CO = CO_new(config_ptr, &heapMemoryUsed);
|
||||
if (CO == NULL)
|
||||
{
|
||||
log_printf("Error: Can't allocate memory\n");
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
log_printf("Allocated %u bytes for CANopen objects\n", heapMemoryUsed);
|
||||
}
|
||||
|
||||
canopenNodeSTM32->canOpenStack = CO;
|
||||
|
||||
#if (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE
|
||||
err = CO_storageBlank_init(&storage, CO->CANmodule, OD_ENTRY_H1010_storeParameters,
|
||||
OD_ENTRY_H1011_restoreDefaultParameters, storageEntries, storageEntriesCount,
|
||||
&storageInitError);
|
||||
|
||||
if (err != CO_ERROR_NO && err != CO_ERROR_DATA_CORRUPT)
|
||||
{
|
||||
log_printf("Error: Storage %d\n", storageInitError);
|
||||
return 2;
|
||||
}
|
||||
#endif
|
||||
|
||||
canopen_app_resetCommunication();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int canopen_app_resetCommunication(void)
|
||||
{
|
||||
/* CANopen communication reset - initialize CANopen objects *******************/
|
||||
log_printf("CANopenNode - Reset communication...\n");
|
||||
|
||||
/* Wait rt_thread. */
|
||||
CO->CANmodule->CANnormal = false;
|
||||
|
||||
/* Enter CAN configuration. */
|
||||
CO_CANsetConfigurationMode((void *)canopenNodeSTM32);
|
||||
CO_CANmodule_disable(CO->CANmodule);
|
||||
|
||||
/* initialize CANopen */
|
||||
err = CO_CANinit(CO, canopenNodeSTM32, 0); // Bitrate for STM32 microcontroller is being set in MXCube Settings
|
||||
if (err != CO_ERROR_NO)
|
||||
{
|
||||
log_printf("Error: CAN initialization failed: %d\n", err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
CO_LSS_address_t lssAddress = {.identity = {.vendorID = OD_PERSIST_COMM.x1018_identity.vendor_ID,
|
||||
.productCode = OD_PERSIST_COMM.x1018_identity.productCode,
|
||||
.revisionNumber = OD_PERSIST_COMM.x1018_identity.revisionNumber,
|
||||
.serialNumber = OD_PERSIST_COMM.x1018_identity.serialNumber}};
|
||||
err = CO_LSSinit(CO, &lssAddress, &canopenNodeSTM32->desiredNodeID, &canopenNodeSTM32->baudrate);
|
||||
if (err != CO_ERROR_NO)
|
||||
{
|
||||
log_printf("Error: LSS slave initialization failed: %d\n", err);
|
||||
return 2;
|
||||
}
|
||||
|
||||
canopenNodeSTM32->activeNodeID = canopenNodeSTM32->desiredNodeID;
|
||||
uint32_t errInfo = 0;
|
||||
|
||||
err = CO_CANopenInit(CO, /* CANopen object */
|
||||
NULL, /* alternate NMT */
|
||||
NULL, /* alternate em */
|
||||
OD, /* Object dictionary */
|
||||
OD_STATUS_BITS, /* Optional OD_statusBits */
|
||||
NMT_CONTROL, /* CO_NMT_control_t */
|
||||
FIRST_HB_TIME, /* firstHBTime_ms */
|
||||
SDO_SRV_TIMEOUT_TIME, /* SDOserverTimeoutTime_ms */
|
||||
SDO_CLI_TIMEOUT_TIME, /* SDOclientTimeoutTime_ms */
|
||||
SDO_CLI_BLOCK, /* SDOclientBlockTransfer */
|
||||
canopenNodeSTM32->activeNodeID, &errInfo);
|
||||
if (err != CO_ERROR_NO && err != CO_ERROR_NODE_ID_UNCONFIGURED_LSS)
|
||||
{
|
||||
if (err == CO_ERROR_OD_PARAMETERS)
|
||||
{
|
||||
log_printf("Error: Object Dictionary entry 0x%X\n", errInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
log_printf("Error: CANopen initialization failed: %d\n", err);
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
|
||||
err = CO_CANopenInitPDO(CO, CO->em, OD, canopenNodeSTM32->activeNodeID, &errInfo);
|
||||
if (err != CO_ERROR_NO && err != CO_ERROR_NODE_ID_UNCONFIGURED_LSS)
|
||||
{
|
||||
if (err == CO_ERROR_OD_PARAMETERS)
|
||||
{
|
||||
log_printf("Error: Object Dictionary entry 0x%X\n", errInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
log_printf("Error: PDO initialization failed: %d\n", err);
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
|
||||
/* Configure Timer interrupt function for execution every 1 millisecond */
|
||||
HAL_TIM_Base_Start_IT(canopenNodeSTM32->timerHandle); // 1ms interrupt
|
||||
|
||||
/* Configure CAN transmit and receive interrupt */
|
||||
|
||||
/* Configure CANopen callbacks, etc */
|
||||
if (!CO->nodeIdUnconfigured)
|
||||
{
|
||||
|
||||
#if (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE
|
||||
if (storageInitError != 0)
|
||||
{
|
||||
CO_errorReport(CO->em, CO_EM_NON_VOLATILE_MEMORY, CO_EMC_HARDWARE, storageInitError);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
log_printf("CANopenNode - Node-id not initialized\n");
|
||||
}
|
||||
|
||||
/* start CAN */
|
||||
CO_CANsetNormalMode(CO->CANmodule);
|
||||
|
||||
log_printf("CANopenNode - Running...\n");
|
||||
fflush(stdout);
|
||||
time_old = time_current = HAL_GetTick();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void canopen_app_process(void)
|
||||
{
|
||||
/* loop for normal program execution ******************************************/
|
||||
/* get time difference since last function call */
|
||||
time_current = HAL_GetTick();
|
||||
|
||||
if ((time_current - time_old) > 0)
|
||||
{ // Make sure more than 1ms elapsed
|
||||
/* CANopen process */
|
||||
CO_NMT_reset_cmd_t reset_status;
|
||||
uint32_t timeDifference_us = (time_current - time_old) * 1000;
|
||||
time_old = time_current;
|
||||
reset_status = CO_process(CO, false, timeDifference_us, NULL);
|
||||
canopenNodeSTM32->outStatusLEDRed = CO_LED_RED(CO->LEDs, CO_LED_CANopen);
|
||||
canopenNodeSTM32->outStatusLEDGreen = CO_LED_GREEN(CO->LEDs, CO_LED_CANopen);
|
||||
|
||||
if (reset_status == CO_RESET_COMM)
|
||||
{
|
||||
/* delete objects from memory */
|
||||
HAL_TIM_Base_Stop_IT(canopenNodeSTM32->timerHandle);
|
||||
CO_CANsetConfigurationMode((void *)canopenNodeSTM32);
|
||||
CO_delete(CO);
|
||||
log_printf("CANopenNode Reset Communication request\n");
|
||||
canopen_app_init(canopenNodeSTM32); // Reset Communication routine
|
||||
}
|
||||
else if (reset_status == CO_RESET_APP)
|
||||
{
|
||||
log_printf("CANopenNode Device Reset\n");
|
||||
HAL_NVIC_SystemReset(); // Reset the STM32 Microcontroller
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Thread function executes in constant intervals, this function can be called from FreeRTOS tasks or Timers ********/
|
||||
void canopen_app_interrupt(void)
|
||||
{
|
||||
CO_LOCK_OD(CO->CANmodule);
|
||||
if (!CO->nodeIdUnconfigured && CO->CANmodule->CANnormal)
|
||||
{
|
||||
bool_t syncWas = false;
|
||||
/* get time difference since last function call */
|
||||
uint32_t timeDifference_us = 1000; // 1ms second
|
||||
|
||||
#if (CO_CONFIG_SYNC) & CO_CONFIG_SYNC_ENABLE
|
||||
syncWas = CO_process_SYNC(CO, timeDifference_us, NULL);
|
||||
#endif
|
||||
#if (CO_CONFIG_PDO) & CO_CONFIG_RPDO_ENABLE
|
||||
CO_process_RPDO(CO, syncWas, timeDifference_us, NULL);
|
||||
#endif
|
||||
#if (CO_CONFIG_PDO) & CO_CONFIG_TPDO_ENABLE
|
||||
CO_process_TPDO(CO, syncWas, timeDifference_us, NULL);
|
||||
|
||||
#endif
|
||||
}
|
||||
CO_UNLOCK_OD(CO->CANmodule);
|
||||
}
|
||||
74
Middleware/CANopenNode/CO_driver_STM32/CO_app_STM32.h
Normal file
74
Middleware/CANopenNode/CO_driver_STM32/CO_app_STM32.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* CO_app_STM32.h
|
||||
*
|
||||
* Created on: Aug 7, 2022
|
||||
* Author: hamed
|
||||
*/
|
||||
|
||||
#ifndef CANOPENSTM32_CO_APP_STM32_H_
|
||||
#define CANOPENSTM32_CO_APP_STM32_H_
|
||||
|
||||
#include "CANopen.h"
|
||||
#include "main.h"
|
||||
|
||||
/* CANHandle : Pass in the CAN Handle to this function and it wil be used for all CAN Communications. It can be FDCan or CAN
|
||||
* and CANOpenSTM32 Driver will take of care of handling that
|
||||
* HWInitFunction : Pass in the function that initialize the CAN peripheral, usually MX_CAN_Init
|
||||
* timerHandle : Pass in the timer that is going to be used for generating 1ms interrupt for tmrThread function,
|
||||
* please note that CANOpenSTM32 Library will override HAL_TIM_PeriodElapsedCallback function, if you also need this function
|
||||
* in your codes, please take required steps
|
||||
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t
|
||||
desiredNodeID; /*This is the Node ID that you ask the CANOpen stack to assign to your device, although it might not always
|
||||
* be the final NodeID, after calling canopen_app_init() you should check ActiveNodeID of CANopenNodeSTM32 structure for assigned Node ID.
|
||||
*/
|
||||
uint8_t activeNodeID; /* Assigned Node ID */
|
||||
uint16_t baudrate; /* This is the baudrate you've set in your CubeMX Configuration */
|
||||
TIM_HandleTypeDef*
|
||||
timerHandle; /*Pass in the timer that is going to be used for generating 1ms interrupt for tmrThread function,
|
||||
* please note that CANOpenSTM32 Library will override HAL_TIM_PeriodElapsedCallback function, if you also need this function in your codes, please take required steps
|
||||
*/
|
||||
|
||||
/* Pass in the CAN Handle to this function and it wil be used for all CAN Communications. It can be FDCan or CAN
|
||||
* and CANOpenSTM32 Driver will take of care of handling that*/
|
||||
#ifdef CO_STM32_FDCAN_Driver
|
||||
FDCAN_HandleTypeDef* CANHandle;
|
||||
#else
|
||||
CAN_HandleTypeDef* CANHandle;
|
||||
#endif
|
||||
|
||||
void (*HWInitFunction)(); /* Pass in the function that initialize the CAN peripheral, usually MX_CAN_Init */
|
||||
|
||||
uint8_t outStatusLEDGreen; // This will be updated by the stack - Use them for the LED management
|
||||
uint8_t outStatusLEDRed; // This will be updated by the stack - Use them for the LED management
|
||||
CO_t* canOpenStack;
|
||||
|
||||
} CANopenNodeSTM32;
|
||||
|
||||
|
||||
// In order to use CANOpenSTM32, you'll have it have a canopenNodeSTM32 structure somewhere in your codes, it is usually residing in CO_app_STM32.c
|
||||
extern CANopenNodeSTM32* canopenNodeSTM32;
|
||||
|
||||
|
||||
/* This function will initialize the required CANOpen Stack objects, allocate the memory and prepare stack for communication reset*/
|
||||
int canopen_app_init(CANopenNodeSTM32* canopenSTM32);
|
||||
/* This function will reset the CAN communication periperhal and also the CANOpen stack variables */
|
||||
int canopen_app_resetCommunication(void);
|
||||
/* This function will check the input buffers and any outstanding tasks that are not time critical, this function should be called regurarly from your code (i.e from your while(1))*/
|
||||
void canopen_app_process(void);
|
||||
/* Thread function executes in constant intervals, this function can be called from FreeRTOS tasks or Timers ********/
|
||||
void canopen_app_interrupt(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CANOPENSTM32_CO_APP_STM32_H_ */
|
||||
777
Middleware/CANopenNode/CO_driver_STM32/CO_driver_STM32.c
Normal file
777
Middleware/CANopenNode/CO_driver_STM32/CO_driver_STM32.c
Normal file
@@ -0,0 +1,777 @@
|
||||
/*
|
||||
* CAN module object for STM32 (FD)CAN peripheral IP.
|
||||
*
|
||||
* This file is a template for other microcontrollers.
|
||||
*
|
||||
* @file CO_driver.c
|
||||
* @ingroup CO_driver
|
||||
* @author Hamed Jafarzadeh 2022
|
||||
* Tilen Marjerle 2021
|
||||
* Janez Paternoster 2020
|
||||
* @copyright 2004 - 2020 Janez Paternoster
|
||||
*
|
||||
* This file is part of CANopenNode, an opensource CANopen Stack.
|
||||
* Project home page is <https://github.com/CANopenNode/CANopenNode>.
|
||||
* For more information on CANopen see <http://www.can-cia.org/>.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Implementation Author: Tilen Majerle <tilen@majerle.eu>
|
||||
*/
|
||||
#include "301/CO_driver.h"
|
||||
#include "CO_app_STM32.h"
|
||||
#include "stdio.h"
|
||||
|
||||
/* Local CAN module object */
|
||||
static CO_CANmodule_t *CANModule_local = NULL; /* Local instance of global CAN module */
|
||||
|
||||
/* CAN masks for identifiers */
|
||||
#define CANID_MASK 0x07FF /*!< CAN standard ID mask */
|
||||
#define FLAG_RTR 0x8000 /*!< RTR flag, part of identifier */
|
||||
|
||||
/******************************************************************************/
|
||||
void CO_CANsetConfigurationMode(void *CANptr)
|
||||
{
|
||||
/* Put CAN module in configuration mode */
|
||||
if (CANptr != NULL)
|
||||
{
|
||||
#ifdef CO_STM32_FDCAN_Driver
|
||||
HAL_FDCAN_Stop(((CANopenNodeSTM32 *)CANptr)->CANHandle);
|
||||
#else
|
||||
HAL_CAN_Stop(((CANopenNodeSTM32 *)CANptr)->CANHandle);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
void CO_CANsetNormalMode(CO_CANmodule_t *CANmodule)
|
||||
{
|
||||
/* Put CAN module in normal mode */
|
||||
if (CANmodule->CANptr != NULL)
|
||||
{
|
||||
#ifdef CO_STM32_FDCAN_Driver
|
||||
if (HAL_FDCAN_Start(((CANopenNodeSTM32 *)CANmodule->CANptr)->CANHandle) == HAL_OK)
|
||||
#else
|
||||
if (HAL_CAN_Start(((CANopenNodeSTM32 *)CANmodule->CANptr)->CANHandle) == HAL_OK)
|
||||
#endif
|
||||
{
|
||||
CANmodule->CANnormal = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
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)
|
||||
{
|
||||
|
||||
/* verify arguments */
|
||||
if (CANmodule == NULL || rxArray == NULL || txArray == NULL)
|
||||
{
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* Hold CANModule variable */
|
||||
CANmodule->CANptr = CANptr;
|
||||
|
||||
/* Keep a local copy of CANModule */
|
||||
CANModule_local = CANmodule;
|
||||
|
||||
/* Configure object variables */
|
||||
CANmodule->rxArray = rxArray;
|
||||
CANmodule->rxSize = rxSize;
|
||||
CANmodule->txArray = txArray;
|
||||
CANmodule->txSize = txSize;
|
||||
CANmodule->CANerrorStatus = 0;
|
||||
CANmodule->CANnormal = false;
|
||||
CANmodule->useCANrxFilters = false; /* Do not use HW filters */
|
||||
CANmodule->bufferInhibitFlag = false;
|
||||
CANmodule->firstCANtxMessage = true;
|
||||
CANmodule->CANtxCount = 0U;
|
||||
CANmodule->errOld = 0U;
|
||||
|
||||
/* Reset all variables */
|
||||
for (uint16_t i = 0U; i < rxSize; i++)
|
||||
{
|
||||
rxArray[i].ident = 0U;
|
||||
rxArray[i].mask = 0xFFFFU;
|
||||
rxArray[i].object = NULL;
|
||||
rxArray[i].CANrx_callback = NULL;
|
||||
}
|
||||
for (uint16_t i = 0U; i < txSize; i++)
|
||||
{
|
||||
txArray[i].bufferFull = false;
|
||||
}
|
||||
|
||||
/***************************************/
|
||||
/* STM32 related configuration */
|
||||
/***************************************/
|
||||
((CANopenNodeSTM32 *)CANptr)->HWInitFunction();
|
||||
|
||||
/*
|
||||
* Configure global filter that is used as last check if message did not pass any of other filters:
|
||||
*
|
||||
* We do not rely on hardware filters in this example
|
||||
* and are performing software filters instead
|
||||
*
|
||||
* Accept non-matching standard ID messages
|
||||
* Reject non-matching extended ID messages
|
||||
*/
|
||||
|
||||
#ifdef CO_STM32_FDCAN_Driver
|
||||
if (HAL_FDCAN_ConfigGlobalFilter(((CANopenNodeSTM32 *)CANptr)->CANHandle, FDCAN_ACCEPT_IN_RX_FIFO0, FDCAN_REJECT,
|
||||
FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE) != HAL_OK)
|
||||
{
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
#else
|
||||
CAN_FilterTypeDef FilterConfig;
|
||||
#if defined(CAN)
|
||||
FilterConfig.FilterBank = 0;
|
||||
#else
|
||||
if (((CAN_HandleTypeDef *)((CANopenNodeSTM32 *)CANmodule->CANptr)->CANHandle)->Instance == CAN1)
|
||||
{
|
||||
FilterConfig.FilterBank = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
FilterConfig.FilterBank = 14;
|
||||
}
|
||||
#endif
|
||||
FilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
|
||||
FilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
|
||||
FilterConfig.FilterIdHigh = 0x0;
|
||||
FilterConfig.FilterIdLow = 0x0;
|
||||
FilterConfig.FilterMaskIdHigh = 0x0;
|
||||
FilterConfig.FilterMaskIdLow = 0x0;
|
||||
FilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
|
||||
|
||||
FilterConfig.FilterActivation = ENABLE;
|
||||
FilterConfig.SlaveStartFilterBank = 14;
|
||||
|
||||
if (HAL_CAN_ConfigFilter(((CANopenNodeSTM32 *)CANptr)->CANHandle, &FilterConfig) != HAL_OK)
|
||||
{
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
#endif
|
||||
/* Enable notifications */
|
||||
/* Activate the CAN notification interrupts */
|
||||
#ifdef CO_STM32_FDCAN_Driver
|
||||
if (HAL_FDCAN_ActivateNotification(((CANopenNodeSTM32 *)CANptr)->CANHandle,
|
||||
0 | FDCAN_IT_RX_FIFO0_NEW_MESSAGE | FDCAN_IT_RX_FIFO1_NEW_MESSAGE | FDCAN_IT_TX_COMPLETE | FDCAN_IT_TX_FIFO_EMPTY | FDCAN_IT_BUS_OFF | FDCAN_IT_ARB_PROTOCOL_ERROR | FDCAN_IT_DATA_PROTOCOL_ERROR | FDCAN_IT_ERROR_PASSIVE | FDCAN_IT_ERROR_WARNING,
|
||||
0xFFFFFFFF) != HAL_OK)
|
||||
{
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
#else
|
||||
if (HAL_CAN_ActivateNotification(((CANopenNodeSTM32 *)CANptr)->CANHandle, CAN_IT_RX_FIFO0_MSG_PENDING | CAN_IT_RX_FIFO1_MSG_PENDING | CAN_IT_TX_MAILBOX_EMPTY) != HAL_OK)
|
||||
{
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
#endif
|
||||
|
||||
return CO_ERROR_NO;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
void CO_CANmodule_disable(CO_CANmodule_t *CANmodule)
|
||||
{
|
||||
if (CANmodule != NULL && CANmodule->CANptr != NULL)
|
||||
{
|
||||
#ifdef CO_STM32_FDCAN_Driver
|
||||
HAL_FDCAN_Stop(((CANopenNodeSTM32 *)CANmodule->CANptr)->CANHandle);
|
||||
|
||||
#else
|
||||
HAL_CAN_Stop(((CANopenNodeSTM32 *)CANmodule->CANptr)->CANHandle);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
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))
|
||||
{
|
||||
CO_ReturnError_t ret = CO_ERROR_NO;
|
||||
|
||||
if (CANmodule != NULL && object != NULL && CANrx_callback != NULL && index < CANmodule->rxSize)
|
||||
{
|
||||
CO_CANrx_t *buffer = &CANmodule->rxArray[index];
|
||||
|
||||
/* Configure object variables */
|
||||
buffer->object = object;
|
||||
buffer->CANrx_callback = CANrx_callback;
|
||||
|
||||
/*
|
||||
* Configure global identifier, including RTR bit
|
||||
*
|
||||
* This is later used for RX operation match case
|
||||
*/
|
||||
buffer->ident = (ident & CANID_MASK) | (rtr ? FLAG_RTR : 0x00);
|
||||
buffer->mask = (mask & CANID_MASK) | FLAG_RTR;
|
||||
|
||||
/* Set CAN hardware module filter and mask. */
|
||||
if (CANmodule->useCANrxFilters)
|
||||
{
|
||||
__NOP();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
CO_CANtx_t *
|
||||
CO_CANtxBufferInit(CO_CANmodule_t *CANmodule, uint16_t index, uint16_t ident, bool_t rtr, uint8_t noOfBytes,
|
||||
bool_t syncFlag)
|
||||
{
|
||||
CO_CANtx_t *buffer = NULL;
|
||||
|
||||
if (CANmodule != NULL && index < CANmodule->txSize)
|
||||
{
|
||||
buffer = &CANmodule->txArray[index];
|
||||
|
||||
/* CAN identifier, DLC and rtr, bit aligned with CAN module transmit buffer */
|
||||
buffer->ident = ((uint32_t)ident & CANID_MASK) | ((uint32_t)(rtr ? FLAG_RTR : 0x00));
|
||||
buffer->DLC = noOfBytes;
|
||||
buffer->bufferFull = false;
|
||||
buffer->syncFlag = syncFlag;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Send CAN message to network
|
||||
* This function must be called with atomic access.
|
||||
*
|
||||
* \param[in] CANmodule: CAN module instance
|
||||
* \param[in] buffer: Pointer to buffer to transmit
|
||||
*/
|
||||
static uint8_t
|
||||
prv_send_can_message(CO_CANmodule_t *CANmodule, CO_CANtx_t *buffer)
|
||||
{
|
||||
|
||||
uint8_t success = 0;
|
||||
|
||||
/* Check if TX FIFO is ready to accept more messages */
|
||||
#ifdef CO_STM32_FDCAN_Driver
|
||||
static FDCAN_TxHeaderTypeDef tx_hdr;
|
||||
if (HAL_FDCAN_GetTxFifoFreeLevel(((CANopenNodeSTM32 *)CANmodule->CANptr)->CANHandle) > 0)
|
||||
{
|
||||
/*
|
||||
* RTR flag is part of identifier value
|
||||
* hence it needs to be properly decoded
|
||||
*/
|
||||
tx_hdr.Identifier = buffer->ident & CANID_MASK;
|
||||
tx_hdr.TxFrameType = (buffer->ident & FLAG_RTR) ? FDCAN_REMOTE_FRAME : FDCAN_DATA_FRAME;
|
||||
tx_hdr.IdType = FDCAN_STANDARD_ID;
|
||||
tx_hdr.FDFormat = FDCAN_CLASSIC_CAN;
|
||||
tx_hdr.BitRateSwitch = FDCAN_BRS_OFF;
|
||||
tx_hdr.MessageMarker = 0;
|
||||
tx_hdr.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
|
||||
tx_hdr.TxEventFifoControl = FDCAN_NO_TX_EVENTS;
|
||||
|
||||
switch (buffer->DLC)
|
||||
{
|
||||
case 0:
|
||||
tx_hdr.DataLength = FDCAN_DLC_BYTES_0;
|
||||
break;
|
||||
case 1:
|
||||
tx_hdr.DataLength = FDCAN_DLC_BYTES_1;
|
||||
break;
|
||||
case 2:
|
||||
tx_hdr.DataLength = FDCAN_DLC_BYTES_2;
|
||||
break;
|
||||
case 3:
|
||||
tx_hdr.DataLength = FDCAN_DLC_BYTES_3;
|
||||
break;
|
||||
case 4:
|
||||
tx_hdr.DataLength = FDCAN_DLC_BYTES_4;
|
||||
break;
|
||||
case 5:
|
||||
tx_hdr.DataLength = FDCAN_DLC_BYTES_5;
|
||||
break;
|
||||
case 6:
|
||||
tx_hdr.DataLength = FDCAN_DLC_BYTES_6;
|
||||
break;
|
||||
case 7:
|
||||
tx_hdr.DataLength = FDCAN_DLC_BYTES_7;
|
||||
break;
|
||||
case 8:
|
||||
tx_hdr.DataLength = FDCAN_DLC_BYTES_8;
|
||||
break;
|
||||
default: /* Hard error... */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Now add message to FIFO. Should not fail */
|
||||
success =
|
||||
HAL_FDCAN_AddMessageToTxFifoQ(((CANopenNodeSTM32 *)CANmodule->CANptr)->CANHandle, &tx_hdr, buffer->data) == HAL_OK;
|
||||
}
|
||||
#else
|
||||
static CAN_TxHeaderTypeDef tx_hdr;
|
||||
/* Check if TX FIFO is ready to accept more messages */
|
||||
if (HAL_CAN_GetTxMailboxesFreeLevel(((CANopenNodeSTM32 *)CANmodule->CANptr)->CANHandle) > 0)
|
||||
{
|
||||
/*
|
||||
* RTR flag is part of identifier value
|
||||
* hence it needs to be properly decoded
|
||||
*/
|
||||
tx_hdr.ExtId = 0u;
|
||||
tx_hdr.IDE = CAN_ID_STD;
|
||||
tx_hdr.DLC = buffer->DLC;
|
||||
tx_hdr.StdId = buffer->ident & CANID_MASK;
|
||||
tx_hdr.RTR = (buffer->ident & FLAG_RTR) ? CAN_RTR_REMOTE : CAN_RTR_DATA;
|
||||
|
||||
uint32_t TxMailboxNum; // Transmission MailBox number
|
||||
|
||||
/* Now add message to FIFO. Should not fail */
|
||||
success = HAL_CAN_AddTxMessage(((CANopenNodeSTM32 *)CANmodule->CANptr)->CANHandle, &tx_hdr, buffer->data,
|
||||
&TxMailboxNum) == HAL_OK;
|
||||
}
|
||||
#endif
|
||||
return success;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
CO_ReturnError_t
|
||||
CO_CANsend(CO_CANmodule_t *CANmodule, CO_CANtx_t *buffer)
|
||||
{
|
||||
CO_ReturnError_t err = CO_ERROR_NO;
|
||||
|
||||
/* Verify overflow */
|
||||
if (buffer->bufferFull)
|
||||
{
|
||||
if (!CANmodule->firstCANtxMessage)
|
||||
{
|
||||
/* don't set error, if bootup message is still on buffers */
|
||||
CANmodule->CANerrorStatus |= CO_CAN_ERRTX_OVERFLOW;
|
||||
}
|
||||
err = CO_ERROR_TX_OVERFLOW;
|
||||
}
|
||||
|
||||
/*
|
||||
* Send message to CAN network
|
||||
*
|
||||
* Lock interrupts for atomic operation
|
||||
*/
|
||||
CO_LOCK_CAN_SEND(CANmodule);
|
||||
if (prv_send_can_message(CANmodule, buffer))
|
||||
{
|
||||
CANmodule->bufferInhibitFlag = buffer->syncFlag;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Only increment count if buffer wasn't already full */
|
||||
if (!buffer->bufferFull)
|
||||
{
|
||||
buffer->bufferFull = true;
|
||||
CANmodule->CANtxCount++;
|
||||
}
|
||||
}
|
||||
CO_UNLOCK_CAN_SEND(CANmodule);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
void CO_CANclearPendingSyncPDOs(CO_CANmodule_t *CANmodule)
|
||||
{
|
||||
uint32_t tpdoDeleted = 0U;
|
||||
|
||||
CO_LOCK_CAN_SEND(CANmodule);
|
||||
/* Abort message from CAN module, if there is synchronous TPDO.
|
||||
* Take special care with this functionality. */
|
||||
if (/*messageIsOnCanBuffer && */ CANmodule->bufferInhibitFlag)
|
||||
{
|
||||
/* clear TXREQ */
|
||||
CANmodule->bufferInhibitFlag = false;
|
||||
tpdoDeleted = 1U;
|
||||
}
|
||||
/* delete also pending synchronous TPDOs in TX buffers */
|
||||
if (CANmodule->CANtxCount > 0)
|
||||
{
|
||||
for (uint16_t i = CANmodule->txSize; i > 0U; --i)
|
||||
{
|
||||
if (CANmodule->txArray[i].bufferFull)
|
||||
{
|
||||
if (CANmodule->txArray[i].syncFlag)
|
||||
{
|
||||
CANmodule->txArray[i].bufferFull = false;
|
||||
CANmodule->CANtxCount--;
|
||||
tpdoDeleted = 2U;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CO_UNLOCK_CAN_SEND(CANmodule);
|
||||
if (tpdoDeleted)
|
||||
{
|
||||
CANmodule->CANerrorStatus |= CO_CAN_ERRTX_PDO_LATE;
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
/* Get error counters from the module. If necessary, function may use
|
||||
* different way to determine errors. */
|
||||
static uint16_t rxErrors = 0, txErrors = 0, overflow = 0;
|
||||
|
||||
void CO_CANmodule_process(CO_CANmodule_t *CANmodule)
|
||||
{
|
||||
uint32_t err = 0;
|
||||
|
||||
// CANOpen just care about Bus_off, Warning, Passive and Overflow
|
||||
// I didn't find overflow error register in STM32, if you find it please let me know
|
||||
|
||||
#ifdef CO_STM32_FDCAN_Driver
|
||||
|
||||
err = ((FDCAN_HandleTypeDef *)((CANopenNodeSTM32 *)CANmodule->CANptr)->CANHandle)->Instance->PSR & (FDCAN_PSR_BO | FDCAN_PSR_EW | FDCAN_PSR_EP);
|
||||
|
||||
if (CANmodule->errOld != err)
|
||||
{
|
||||
|
||||
uint16_t status = CANmodule->CANerrorStatus;
|
||||
|
||||
CANmodule->errOld = err;
|
||||
|
||||
if (err & FDCAN_PSR_BO)
|
||||
{
|
||||
status |= CO_CAN_ERRTX_BUS_OFF;
|
||||
// In this driver we expect that the controller is automatically handling the protocol exceptions.
|
||||
}
|
||||
else
|
||||
{
|
||||
/* recalculate CANerrorStatus, first clear some flags */
|
||||
status &= 0xFFFF ^ (CO_CAN_ERRTX_BUS_OFF | CO_CAN_ERRRX_WARNING | CO_CAN_ERRRX_PASSIVE | CO_CAN_ERRTX_WARNING | CO_CAN_ERRTX_PASSIVE);
|
||||
|
||||
if (err & FDCAN_PSR_EW)
|
||||
{
|
||||
status |= CO_CAN_ERRRX_WARNING | CO_CAN_ERRTX_WARNING;
|
||||
}
|
||||
|
||||
if (err & FDCAN_PSR_EP)
|
||||
{
|
||||
status |= CO_CAN_ERRRX_PASSIVE | CO_CAN_ERRTX_PASSIVE;
|
||||
}
|
||||
}
|
||||
|
||||
CANmodule->CANerrorStatus = status;
|
||||
}
|
||||
#else
|
||||
|
||||
err = ((CAN_HandleTypeDef *)((CANopenNodeSTM32 *)CANmodule->CANptr)->CANHandle)->Instance->ESR & (CAN_ESR_BOFF | CAN_ESR_EPVF | CAN_ESR_EWGF);
|
||||
|
||||
// uint32_t esrVal = ((CAN_HandleTypeDef*)((CANopenNodeSTM32*)CANmodule->CANptr)->CANHandle)->Instance->ESR; Debug purpose
|
||||
if (CANmodule->errOld != err)
|
||||
{
|
||||
|
||||
uint16_t status = CANmodule->CANerrorStatus;
|
||||
|
||||
CANmodule->errOld = err;
|
||||
|
||||
if (err & CAN_ESR_BOFF)
|
||||
{
|
||||
status |= CO_CAN_ERRTX_BUS_OFF;
|
||||
// In this driver, we assume that auto bus recovery is activated ! so this error will eventually handled automatically.
|
||||
}
|
||||
else
|
||||
{
|
||||
/* recalculate CANerrorStatus, first clear some flags */
|
||||
status &= 0xFFFF ^ (CO_CAN_ERRTX_BUS_OFF | CO_CAN_ERRRX_WARNING | CO_CAN_ERRRX_PASSIVE | CO_CAN_ERRTX_WARNING | CO_CAN_ERRTX_PASSIVE);
|
||||
|
||||
if (err & CAN_ESR_EWGF)
|
||||
{
|
||||
status |= CO_CAN_ERRRX_WARNING | CO_CAN_ERRTX_WARNING;
|
||||
}
|
||||
|
||||
if (err & CAN_ESR_EPVF)
|
||||
{
|
||||
status |= CO_CAN_ERRRX_PASSIVE | CO_CAN_ERRTX_PASSIVE;
|
||||
}
|
||||
}
|
||||
|
||||
CANmodule->CANerrorStatus = status;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Read message from RX FIFO
|
||||
* \param hfdcan: pointer to an FDCAN_HandleTypeDef structure that contains
|
||||
* the configuration information for the specified FDCAN.
|
||||
* \param[in] fifo: Fifo number to use for read
|
||||
* \param[in] fifo_isrs: List of interrupts for respected FIFO
|
||||
*/
|
||||
#ifdef CO_STM32_FDCAN_Driver
|
||||
static void
|
||||
prv_read_can_received_msg(FDCAN_HandleTypeDef *hfdcan, uint32_t fifo, uint32_t fifo_isrs)
|
||||
#else
|
||||
static void
|
||||
prv_read_can_received_msg(CAN_HandleTypeDef *hcan, uint32_t fifo, uint32_t fifo_isrs)
|
||||
#endif
|
||||
{
|
||||
|
||||
CO_CANrxMsg_t rcvMsg;
|
||||
CO_CANrx_t *buffer = NULL; /* receive message buffer from CO_CANmodule_t object. */
|
||||
uint16_t index; /* index of received message */
|
||||
uint32_t rcvMsgIdent; /* identifier of the received message */
|
||||
uint8_t messageFound = 0;
|
||||
|
||||
#ifdef CO_STM32_FDCAN_Driver
|
||||
static FDCAN_RxHeaderTypeDef rx_hdr;
|
||||
/* Read received message from FIFO */
|
||||
if (HAL_FDCAN_GetRxMessage(hfdcan, fifo, &rx_hdr, rcvMsg.data) != HAL_OK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
/* Setup identifier (with RTR) and length */
|
||||
rcvMsg.ident = rx_hdr.Identifier | (rx_hdr.RxFrameType == FDCAN_REMOTE_FRAME ? FLAG_RTR : 0x00);
|
||||
switch (rx_hdr.DataLength)
|
||||
{
|
||||
case FDCAN_DLC_BYTES_0:
|
||||
rcvMsg.dlc = 0;
|
||||
break;
|
||||
case FDCAN_DLC_BYTES_1:
|
||||
rcvMsg.dlc = 1;
|
||||
break;
|
||||
case FDCAN_DLC_BYTES_2:
|
||||
rcvMsg.dlc = 2;
|
||||
break;
|
||||
case FDCAN_DLC_BYTES_3:
|
||||
rcvMsg.dlc = 3;
|
||||
break;
|
||||
case FDCAN_DLC_BYTES_4:
|
||||
rcvMsg.dlc = 4;
|
||||
break;
|
||||
case FDCAN_DLC_BYTES_5:
|
||||
rcvMsg.dlc = 5;
|
||||
break;
|
||||
case FDCAN_DLC_BYTES_6:
|
||||
rcvMsg.dlc = 6;
|
||||
break;
|
||||
case FDCAN_DLC_BYTES_7:
|
||||
rcvMsg.dlc = 7;
|
||||
break;
|
||||
case FDCAN_DLC_BYTES_8:
|
||||
rcvMsg.dlc = 8;
|
||||
break;
|
||||
default:
|
||||
rcvMsg.dlc = 0;
|
||||
break; /* Invalid length when more than 8 */
|
||||
}
|
||||
rcvMsgIdent = rcvMsg.ident;
|
||||
#else
|
||||
static CAN_RxHeaderTypeDef rx_hdr;
|
||||
/* Read received message from FIFO */
|
||||
if (HAL_CAN_GetRxMessage(hcan, fifo, &rx_hdr, rcvMsg.data) != HAL_OK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* Setup identifier (with RTR) and length */
|
||||
rcvMsg.ident = rx_hdr.StdId | (rx_hdr.RTR == CAN_RTR_REMOTE ? FLAG_RTR : 0x00);
|
||||
rcvMsg.dlc = rx_hdr.DLC;
|
||||
rcvMsgIdent = rcvMsg.ident;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Hardware filters are not used for the moment
|
||||
* \todo: Implement hardware filters...
|
||||
*/
|
||||
if (CANModule_local->useCANrxFilters)
|
||||
{
|
||||
__BKPT(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* We are not using hardware filters, hence it is necessary
|
||||
* to manually match received message ID with all buffers
|
||||
*/
|
||||
buffer = CANModule_local->rxArray;
|
||||
for (index = CANModule_local->rxSize; index > 0U; --index, ++buffer)
|
||||
{
|
||||
if (((rcvMsgIdent ^ buffer->ident) & buffer->mask) == 0U)
|
||||
{
|
||||
messageFound = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Call specific function, which will process the message */
|
||||
if (messageFound && buffer != NULL && buffer->CANrx_callback != NULL)
|
||||
{
|
||||
buffer->CANrx_callback(buffer->object, (void *)&rcvMsg);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CO_STM32_FDCAN_Driver
|
||||
/**
|
||||
* \brief Rx FIFO 0 callback.
|
||||
* \param[in] hfdcan: pointer to an FDCAN_HandleTypeDef structure that contains
|
||||
* the configuration information for the specified FDCAN.
|
||||
* \param[in] RxFifo0ITs: indicates which Rx FIFO 0 interrupts are signaled.
|
||||
*/
|
||||
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
|
||||
{
|
||||
if (RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE)
|
||||
{
|
||||
prv_read_can_received_msg(hfdcan, FDCAN_RX_FIFO0, RxFifo0ITs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Rx FIFO 1 callback.
|
||||
* \param[in] hfdcan: pointer to an FDCAN_HandleTypeDef structure that contains
|
||||
* the configuration information for the specified FDCAN.
|
||||
* \param[in] RxFifo1ITs: indicates which Rx FIFO 0 interrupts are signaled.
|
||||
*/
|
||||
void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs)
|
||||
{
|
||||
if (RxFifo1ITs & FDCAN_IT_RX_FIFO1_NEW_MESSAGE)
|
||||
{
|
||||
prv_read_can_received_msg(hfdcan, FDCAN_RX_FIFO1, RxFifo1ITs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief TX buffer has been well transmitted callback
|
||||
* \param[in] hfdcan: pointer to an FDCAN_HandleTypeDef structure that contains
|
||||
* the configuration information for the specified FDCAN.
|
||||
* \param[in] BufferIndexes: Bits of successfully sent TX buffers
|
||||
*/
|
||||
void HAL_FDCAN_TxBufferCompleteCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t BufferIndexes)
|
||||
{
|
||||
CANModule_local->firstCANtxMessage = false; /* First CAN message (bootup) was sent successfully */
|
||||
CANModule_local->bufferInhibitFlag = false; /* Clear flag from previous message */
|
||||
if (CANModule_local->CANtxCount > 0U)
|
||||
{ /* Are there any new messages waiting to be send */
|
||||
CO_CANtx_t *buffer = &CANModule_local->txArray[0]; /* Start with first buffer handle */
|
||||
uint16_t i;
|
||||
|
||||
/*
|
||||
* Try to send more buffers, process all empty ones
|
||||
*
|
||||
* This function is always called from interrupt,
|
||||
* however to make sure no preemption can happen, interrupts are anyway locked
|
||||
* (unless you can guarantee no higher priority interrupt will try to access to FDCAN instance and send data,
|
||||
* then no need to lock interrupts..)
|
||||
*/
|
||||
CO_LOCK_CAN_SEND(CANModule_local);
|
||||
for (i = CANModule_local->txSize; i > 0U; --i, ++buffer)
|
||||
{
|
||||
/* Try to send message */
|
||||
if (buffer->bufferFull)
|
||||
{
|
||||
if (prv_send_can_message(CANModule_local, buffer))
|
||||
{
|
||||
buffer->bufferFull = false;
|
||||
CANModule_local->CANtxCount--;
|
||||
CANModule_local->bufferInhibitFlag = buffer->syncFlag;
|
||||
}
|
||||
else
|
||||
{
|
||||
break; // if we could not send the message, break out of the loop (the tx buffers are full)
|
||||
}
|
||||
}
|
||||
}
|
||||
CO_UNLOCK_CAN_SEND(CANModule_local);
|
||||
}
|
||||
}
|
||||
#else
|
||||
/**
|
||||
* \brief Rx FIFO 0 callback.
|
||||
* \param[in] hcan: pointer to an CAN_HandleTypeDef structure that contains
|
||||
* the configuration information for the specified CAN.
|
||||
*/
|
||||
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
|
||||
{
|
||||
prv_read_can_received_msg(hcan, CAN_RX_FIFO0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Rx FIFO 1 callback.
|
||||
* \param[in] hcan: pointer to an CAN_HandleTypeDef structure that contains
|
||||
* the configuration information for the specified CAN.
|
||||
*/
|
||||
void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan)
|
||||
{
|
||||
prv_read_can_received_msg(hcan, CAN_RX_FIFO1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief TX buffer has been well transmitted callback
|
||||
* \param[in] hcan: pointer to an CAN_HandleTypeDef structure that contains
|
||||
* the configuration information for the specified CAN.
|
||||
* \param[in] MailboxNumber: the mailbox number that has been transmitted
|
||||
*/
|
||||
void CO_CANinterrupt_TX(CO_CANmodule_t *CANmodule, uint32_t MailboxNumber)
|
||||
{
|
||||
|
||||
CANmodule->firstCANtxMessage = false; /* First CAN message (bootup) was sent successfully */
|
||||
CANmodule->bufferInhibitFlag = false; /* Clear flag from previous message */
|
||||
if (CANmodule->CANtxCount > 0U)
|
||||
{ /* Are there any new messages waiting to be send */
|
||||
CO_CANtx_t *buffer = &CANmodule->txArray[0]; /* Start with first buffer handle */
|
||||
uint16_t i;
|
||||
|
||||
/*
|
||||
* Try to send more buffers, process all empty ones
|
||||
*
|
||||
* This function is always called from interrupt,
|
||||
* however to make sure no preemption can happen, interrupts are anyway locked
|
||||
* (unless you can guarantee no higher priority interrupt will try to access to CAN instance and send data,
|
||||
* then no need to lock interrupts..)
|
||||
*/
|
||||
CO_LOCK_CAN_SEND(CANmodule);
|
||||
for (i = CANmodule->txSize; i > 0U; --i, ++buffer)
|
||||
{
|
||||
/* Try to send message */
|
||||
if (buffer->bufferFull)
|
||||
{
|
||||
if (prv_send_can_message(CANmodule, buffer))
|
||||
{
|
||||
buffer->bufferFull = false;
|
||||
CANmodule->CANtxCount--;
|
||||
CANmodule->bufferInhibitFlag = buffer->syncFlag;
|
||||
}
|
||||
else
|
||||
break; // if we could not send the message, break out of the loop (the tx buffers are full)
|
||||
}
|
||||
}
|
||||
CO_UNLOCK_CAN_SEND(CANmodule);
|
||||
}
|
||||
}
|
||||
|
||||
void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan)
|
||||
{
|
||||
CO_CANinterrupt_TX(CANModule_local, CAN_TX_MAILBOX0);
|
||||
}
|
||||
|
||||
void HAL_CAN_TxMailbox1CompleteCallback(CAN_HandleTypeDef *hcan)
|
||||
{
|
||||
CO_CANinterrupt_TX(CANModule_local, CAN_TX_MAILBOX0);
|
||||
}
|
||||
|
||||
void HAL_CAN_TxMailbox2CompleteCallback(CAN_HandleTypeDef *hcan)
|
||||
{
|
||||
CO_CANinterrupt_TX(CANModule_local, CAN_TX_MAILBOX0);
|
||||
}
|
||||
#endif
|
||||
183
Middleware/CANopenNode/CO_driver_STM32/CO_driver_target.h
Normal file
183
Middleware/CANopenNode/CO_driver_STM32/CO_driver_target.h
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Device and application specific definitions for CANopenNode.
|
||||
*
|
||||
* @file CO_driver_target.h
|
||||
* @author Hamed Jafarzadeh 2022
|
||||
* Tilen Marjerle 2021
|
||||
* Janez Paternoster 2020
|
||||
* @copyright 2004 - 2020 Janez Paternoster
|
||||
*
|
||||
* This file is part of CANopenNode, an opensource CANopen Stack.
|
||||
* Project home page is <https://github.com/CANopenNode/CANopenNode>.
|
||||
* For more information on CANopen see <http://www.can-cia.org/>.
|
||||
*
|
||||
* 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_TARGET_H
|
||||
#define CO_DRIVER_TARGET_H
|
||||
|
||||
/* This file contains device and application specific definitions.
|
||||
* It is included from CO_driver.h, which contains documentation
|
||||
* for common definitions below. */
|
||||
|
||||
#include "main.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Determining the CANOpen Driver
|
||||
|
||||
#if defined(FDCAN) || defined(FDCAN1) || defined(FDCAN2) || defined(FDCAN3)
|
||||
#define CO_STM32_FDCAN_Driver 1
|
||||
#elif defined(CAN) || defined(CAN1) || defined(CAN2) || defined(CAN3)
|
||||
#define CO_STM32_CAN_Driver 1
|
||||
#else
|
||||
#error This STM32 Do not support CAN or FDCAN
|
||||
#endif
|
||||
|
||||
#undef CO_CONFIG_STORAGE_ENABLE // We don't need Storage option, implement based on your use case and remove this line from here
|
||||
|
||||
#ifdef CO_DRIVER_CUSTOM
|
||||
#include "CO_driver_custom.h"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Stack configuration override default values.
|
||||
* For more information see file CO_config.h. */
|
||||
|
||||
/* Basic definitions. If big endian, CO_SWAP_xx macros must swap bytes. */
|
||||
#define CO_LITTLE_ENDIAN
|
||||
#define CO_SWAP_16(x) x
|
||||
#define CO_SWAP_32(x) x
|
||||
#define CO_SWAP_64(x) x
|
||||
|
||||
/* NULL is defined in stddef.h */
|
||||
/* true and false are defined in stdbool.h */
|
||||
/* int8_t to uint64_t are defined in stdint.h */
|
||||
typedef uint_fast8_t bool_t;
|
||||
typedef float float32_t;
|
||||
typedef double float64_t;
|
||||
|
||||
/**
|
||||
* \brief CAN RX message for platform
|
||||
*
|
||||
* This is platform specific one
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t ident; /*!< Standard identifier */
|
||||
uint8_t dlc; /*!< Data length */
|
||||
uint8_t data[8]; /*!< Received data */
|
||||
} CO_CANrxMsg_t;
|
||||
|
||||
/* Access to received CAN message */
|
||||
#define CO_CANrxMsg_readIdent(msg) ((uint16_t)(((CO_CANrxMsg_t*)(msg)))->ident)
|
||||
#define CO_CANrxMsg_readDLC(msg) ((uint8_t)(((CO_CANrxMsg_t*)(msg)))->dlc)
|
||||
#define CO_CANrxMsg_readData(msg) ((uint8_t*)(((CO_CANrxMsg_t*)(msg)))->data)
|
||||
|
||||
/* Received message object */
|
||||
typedef struct {
|
||||
uint16_t ident;
|
||||
uint16_t mask;
|
||||
void* object;
|
||||
void (*CANrx_callback)(void* object, void* message);
|
||||
} CO_CANrx_t;
|
||||
|
||||
/* Transmit message object */
|
||||
typedef struct {
|
||||
uint32_t ident;
|
||||
uint8_t DLC;
|
||||
uint8_t data[8];
|
||||
volatile bool_t bufferFull;
|
||||
volatile bool_t syncFlag;
|
||||
} CO_CANtx_t;
|
||||
|
||||
/* CAN module object */
|
||||
typedef struct {
|
||||
void* CANptr;
|
||||
CO_CANrx_t* rxArray;
|
||||
uint16_t rxSize;
|
||||
CO_CANtx_t* txArray;
|
||||
uint16_t txSize;
|
||||
uint16_t CANerrorStatus;
|
||||
volatile bool_t CANnormal;
|
||||
volatile bool_t useCANrxFilters;
|
||||
volatile bool_t bufferInhibitFlag;
|
||||
volatile bool_t firstCANtxMessage;
|
||||
volatile uint16_t CANtxCount;
|
||||
uint32_t errOld;
|
||||
|
||||
/* STM32 specific features */
|
||||
uint32_t primask_send; /* Primask register for interrupts for send operation */
|
||||
uint32_t primask_emcy; /* Primask register for interrupts for emergency operation */
|
||||
uint32_t primask_od; /* Primask register for interrupts for send operation */
|
||||
|
||||
} CO_CANmodule_t;
|
||||
|
||||
/* Data storage object for one entry */
|
||||
typedef struct {
|
||||
void* addr;
|
||||
size_t len;
|
||||
uint8_t subIndexOD;
|
||||
uint8_t attr;
|
||||
/* Additional variables (target specific) */
|
||||
void* addrNV;
|
||||
} CO_storage_entry_t;
|
||||
|
||||
/* (un)lock critical section in CO_CANsend() */
|
||||
// Why disabling the whole Interrupt
|
||||
#define CO_LOCK_CAN_SEND(CAN_MODULE) \
|
||||
do { \
|
||||
(CAN_MODULE)->primask_send = __get_PRIMASK(); \
|
||||
__disable_irq(); \
|
||||
} while (0)
|
||||
#define CO_UNLOCK_CAN_SEND(CAN_MODULE) __set_PRIMASK((CAN_MODULE)->primask_send)
|
||||
|
||||
/* (un)lock critical section in CO_errorReport() or CO_errorReset() */
|
||||
#define CO_LOCK_EMCY(CAN_MODULE) \
|
||||
do { \
|
||||
(CAN_MODULE)->primask_emcy = __get_PRIMASK(); \
|
||||
__disable_irq(); \
|
||||
} while (0)
|
||||
#define CO_UNLOCK_EMCY(CAN_MODULE) __set_PRIMASK((CAN_MODULE)->primask_emcy)
|
||||
|
||||
/* (un)lock critical section when accessing Object Dictionary */
|
||||
#define CO_LOCK_OD(CAN_MODULE) \
|
||||
do { \
|
||||
(CAN_MODULE)->primask_od = __get_PRIMASK(); \
|
||||
__disable_irq(); \
|
||||
} while (0)
|
||||
#define CO_UNLOCK_OD(CAN_MODULE) __set_PRIMASK((CAN_MODULE)->primask_od)
|
||||
|
||||
/* Synchronization between CAN receive and message processing threads. */
|
||||
#define CO_MemoryBarrier()
|
||||
#define CO_FLAG_READ(rxNew) ((rxNew) != NULL)
|
||||
#define CO_FLAG_SET(rxNew) \
|
||||
do { \
|
||||
CO_MemoryBarrier(); \
|
||||
rxNew = (void*)1L; \
|
||||
} while (0)
|
||||
#define CO_FLAG_CLEAR(rxNew) \
|
||||
do { \
|
||||
CO_MemoryBarrier(); \
|
||||
rxNew = NULL; \
|
||||
} while (0)
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CO_DRIVER_TARGET_H */
|
||||
96
Middleware/CANopenNode/CO_driver_STM32/CO_storageBlank.c
Normal file
96
Middleware/CANopenNode/CO_driver_STM32/CO_storageBlank.c
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* CANopen Object Dictionary storage object (blank example).
|
||||
*
|
||||
* @file CO_storageBlank.c
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2021 Janez Paternoster
|
||||
*
|
||||
* This file is part of CANopenNode, an opensource CANopen Stack.
|
||||
* Project home page is <https://github.com/CANopenNode/CANopenNode>.
|
||||
* For more information on CANopen see <http://www.can-cia.org/>.
|
||||
*
|
||||
* 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 "CO_storageBlank.h"
|
||||
|
||||
#if (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE
|
||||
|
||||
/*
|
||||
* Function for writing data on "Store parameters" command - OD object 1010
|
||||
*
|
||||
* For more information see file CO_storage.h, CO_storage_entry_t.
|
||||
*/
|
||||
static ODR_t
|
||||
storeBlank(CO_storage_entry_t* entry, CO_CANmodule_t* CANmodule) {
|
||||
|
||||
/* Open a file and write data to it */
|
||||
/* file = open(entry->pathToFileOrPointerToMemory); */
|
||||
CO_LOCK_OD(CANmodule);
|
||||
/* write(entry->addr, entry->len, file); */
|
||||
CO_UNLOCK_OD(CANmodule);
|
||||
|
||||
return ODR_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Function for restoring data on "Restore default parameters" command - OD 1011
|
||||
*
|
||||
* For more information see file CO_storage.h, CO_storage_entry_t.
|
||||
*/
|
||||
static ODR_t
|
||||
restoreBlank(CO_storage_entry_t* entry, CO_CANmodule_t* CANmodule) {
|
||||
|
||||
/* disable (delete) the file, so default values will stay after startup */
|
||||
|
||||
return ODR_OK;
|
||||
}
|
||||
|
||||
CO_ReturnError_t
|
||||
CO_storageBlank_init(CO_storage_t* storage, CO_CANmodule_t* CANmodule, OD_entry_t* OD_1010_StoreParameters,
|
||||
OD_entry_t* OD_1011_RestoreDefaultParam, CO_storage_entry_t* entries, uint8_t entriesCount,
|
||||
uint32_t* storageInitError) {
|
||||
CO_ReturnError_t ret;
|
||||
|
||||
/* verify arguments */
|
||||
if (storage == NULL || entries == NULL || entriesCount == 0 || storageInitError == NULL) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* initialize storage and OD extensions */
|
||||
ret = CO_storage_init(storage, CANmodule, OD_1010_StoreParameters, OD_1011_RestoreDefaultParam, storeBlank,
|
||||
restoreBlank, entries, entriesCount);
|
||||
if (ret != CO_ERROR_NO) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* initialize entries */
|
||||
*storageInitError = 0;
|
||||
for (uint8_t i = 0; i < entriesCount; i++) {
|
||||
CO_storage_entry_t* entry = &entries[i];
|
||||
|
||||
/* verify arguments */
|
||||
if (entry->addr == NULL || entry->len == 0 || entry->subIndexOD < 2) {
|
||||
*storageInitError = i;
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* Open a file and read data from file to entry->addr */
|
||||
/* file = open(entry->pathToFileOrPointerToMemory); */
|
||||
/* read(entry->addr, entry->len, file); */
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif /* (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE */
|
||||
58
Middleware/CANopenNode/CO_driver_STM32/CO_storageBlank.h
Normal file
58
Middleware/CANopenNode/CO_driver_STM32/CO_storageBlank.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* CANopen data storage object (blank example)
|
||||
*
|
||||
* @file CO_storageBlank.h
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2021 Janez Paternoster
|
||||
*
|
||||
* This file is part of CANopenNode, an opensource CANopen Stack.
|
||||
* Project home page is <https://github.com/CANopenNode/CANopenNode>.
|
||||
* For more information on CANopen see <http://www.can-cia.org/>.
|
||||
*
|
||||
* 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_STORAGE_BLANK_H
|
||||
#define CO_STORAGE_BLANK_H
|
||||
|
||||
#include "storage/CO_storage.h"
|
||||
|
||||
#if ((CO_CONFIG_STORAGE)&CO_CONFIG_STORAGE_ENABLE) || defined CO_DOXYGEN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* This is very basic example of implementing (object dictionary) data storage.
|
||||
* Data storage is target specific. CO_storageBlank.h and .c files only shows
|
||||
* the basic principle, but does nothing. For complete example of storage see:
|
||||
* - CANopenPIC/PIC32 uses eeprom with CANopenNode/storage/CO_storage.h/.c,
|
||||
* CANopenNode/storage/CO_storageEeprom.h/.c, CANopenNode/storage/CO_eeprom.h
|
||||
* and CANopenPIC/PIC32/CO_eepromPIC32.c files.
|
||||
* - CANopenLinux uses file system with CANopenNode/storage/CO_storage.h/.c and
|
||||
* CANopenLinux/CO_storageLinux.h files.
|
||||
*/
|
||||
|
||||
CO_ReturnError_t CO_storageBlank_init(CO_storage_t* storage, CO_CANmodule_t* CANmodule,
|
||||
OD_entry_t* OD_1010_StoreParameters, OD_entry_t* OD_1011_RestoreDefaultParam,
|
||||
CO_storage_entry_t* entries, uint8_t entriesCount, uint32_t* storageInitError);
|
||||
|
||||
uint32_t CO_storageBlank_auto_process(CO_storage_t* storage, bool_t closeFiles);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE */
|
||||
|
||||
#endif /* CO_STORAGE_BLANK_H */
|
||||
1760
Middleware/CANopenNode/CO_driver_STM32/DS301_profile.eds
Normal file
1760
Middleware/CANopenNode/CO_driver_STM32/DS301_profile.eds
Normal file
File diff suppressed because it is too large
Load Diff
747
Middleware/CANopenNode/CO_driver_STM32/DS301_profile.md
Normal file
747
Middleware/CANopenNode/CO_driver_STM32/DS301_profile.md
Normal file
@@ -0,0 +1,747 @@
|
||||
CANopen device documentation
|
||||
============================
|
||||
**New Product**
|
||||
|
||||
|
||||
|
||||
| | |
|
||||
| ------------ | ------------------------------ |
|
||||
| Project File | DS301_profile.xpd |
|
||||
| File Version | 1 |
|
||||
| Created | 23. 11. 2020 13:00:00 |
|
||||
| Created By | |
|
||||
| Modified | 9. 08. 2021 18:39:55 |
|
||||
| Modified By | |
|
||||
|
||||
This file was automatically generated by [CANopenEditor](https://github.com/CANopenNode/CANopenEditor) v4.0-51-g2d9b1ad
|
||||
|
||||
[TOC]
|
||||
|
||||
|
||||
Device Information
|
||||
------------------
|
||||
| | |
|
||||
| ------------ | ------------------------------ |
|
||||
| Vendor Name | |
|
||||
| Vendor ID | |
|
||||
| Product Name | New Product |
|
||||
| Product ID | |
|
||||
| Granularity | 8 |
|
||||
| RPDO count | 4 |
|
||||
| TPDO count | 4 |
|
||||
| LSS Slave | True |
|
||||
| LSS Master | False |
|
||||
|
||||
#### Supported Baud rates
|
||||
* [x] 10 kBit/s
|
||||
* [x] 20 kBit/s
|
||||
* [x] 50 kBit/s
|
||||
* [x] 125 kBit/s
|
||||
* [x] 250 kBit/s
|
||||
* [x] 500 kBit/s
|
||||
* [x] 800 kBit/s
|
||||
* [x] 1000 kBit/s
|
||||
* [ ] auto
|
||||
|
||||
|
||||
PDO Mapping
|
||||
-----------
|
||||
|
||||
Communication Specific Parameters
|
||||
---------------------------------
|
||||
|
||||
### 0x1000 - Device type
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| VAR | NMT | PERSIST_COMM |
|
||||
|
||||
| Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ----------------------- | --- | --- | ---- | ------------------------------- |
|
||||
| UNSIGNED32 | ro | no | no | 0x00000000 |
|
||||
|
||||
* bit 16-31: Additional information
|
||||
* bit 0-15: Device profile number
|
||||
|
||||
### 0x1001 - Error register
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| VAR | EM | RAM |
|
||||
|
||||
| Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ----------------------- | --- | --- | ---- | ------------------------------- |
|
||||
| UNSIGNED8 | ro | t | no | 0x00 |
|
||||
|
||||
* bit 7: manufacturer specific
|
||||
* bit 6: Reserved (always 0)
|
||||
* bit 5: device profile specific
|
||||
* bit 4: communication error (overrun, error state)
|
||||
* bit 3: temperature
|
||||
* bit 2: voltage
|
||||
* bit 1: current
|
||||
* bit 0: generic error
|
||||
|
||||
### 0x1003 - Pre-defined error field
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| ARRAY | | RAM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Number of errors | UNSIGNED8 | rw | no | no | |
|
||||
| 0x01 | Standard error field | UNSIGNED32 | ro | no | no | |
|
||||
| 0x02 | Standard error field | UNSIGNED32 | ro | no | no | |
|
||||
| 0x03 | Standard error field | UNSIGNED32 | ro | no | no | |
|
||||
| 0x04 | Standard error field | UNSIGNED32 | ro | no | no | |
|
||||
| 0x05 | Standard error field | UNSIGNED32 | ro | no | no | |
|
||||
| 0x06 | Standard error field | UNSIGNED32 | ro | no | no | |
|
||||
| 0x07 | Standard error field | UNSIGNED32 | ro | no | no | |
|
||||
| 0x08 | Standard error field | UNSIGNED32 | ro | no | no | |
|
||||
| 0x09 | Standard error field | UNSIGNED32 | ro | no | no | |
|
||||
| 0x0A | Standard error field | UNSIGNED32 | ro | no | no | |
|
||||
| 0x0B | Standard error field | UNSIGNED32 | ro | no | no | |
|
||||
| 0x0C | Standard error field | UNSIGNED32 | ro | no | no | |
|
||||
| 0x0D | Standard error field | UNSIGNED32 | ro | no | no | |
|
||||
| 0x0E | Standard error field | UNSIGNED32 | ro | no | no | |
|
||||
| 0x0F | Standard error field | UNSIGNED32 | ro | no | no | |
|
||||
| 0x10 | Standard error field | UNSIGNED32 | ro | no | no | |
|
||||
|
||||
* Sub Index 0: Contains number of actual errors. 0 can be written to clear error history.
|
||||
* sub-index 1 and above:
|
||||
* bit 16-31: Manufacturer specific additional information
|
||||
* bit 0-15: Error code as transmited in the Emergency object
|
||||
|
||||
### 0x1005 - COB-ID SYNC message
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| VAR | SYNC | PERSIST_COMM |
|
||||
|
||||
| Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ----------------------- | --- | --- | ---- | ------------------------------- |
|
||||
| UNSIGNED32 | rw | no | no | 0x00000080 |
|
||||
|
||||
* bit 31: set to 0
|
||||
* bit 30: If set, CANopen device generates SYNC object
|
||||
* bit 11-29: set to 0
|
||||
* bit 0-10: 11-bit CAN-ID
|
||||
|
||||
### 0x1006 - Communication cycle period
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| VAR | SYNC_PROD | PERSIST_COMM |
|
||||
|
||||
| Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ----------------------- | --- | --- | ---- | ------------------------------- |
|
||||
| UNSIGNED32 | rw | no | no | 0 |
|
||||
|
||||
Period of SYNC transmission in µs (0 = transmission disabled).
|
||||
|
||||
### 0x1007 - Synchronous window length
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| VAR | | PERSIST_COMM |
|
||||
|
||||
| Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ----------------------- | --- | --- | ---- | ------------------------------- |
|
||||
| UNSIGNED32 | rw | no | no | 0 |
|
||||
|
||||
Synchronous window leghth in µs (0 = not used). All synchronous PDOs must be transmitted within this time window.
|
||||
|
||||
### 0x1010 - Store parameters
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| ARRAY | STORAGE | RAM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Highest sub-index supported| UNSIGNED8 | ro | no | no | 0x04 |
|
||||
| 0x01 | Save all parameters | UNSIGNED32 | rw | no | no | 0x00000001 |
|
||||
| 0x02 | Save communication parameters| UNSIGNED32 | rw | no | no | 0x00000001 |
|
||||
| 0x03 | Save application parameters| UNSIGNED32 | rw | no | no | 0x00000001 |
|
||||
| 0x04 | Save manufacturer defined parameters| UNSIGNED32 | rw | no | no | 0x00000001 |
|
||||
|
||||
Sub-indexes 1 and above:
|
||||
* Reading provides information about its storage functionality:
|
||||
* bit 1: If set, CANopen device saves parameters autonomously
|
||||
* bit 0: If set, CANopen device saves parameters on command
|
||||
* Writing value 0x65766173 ('s','a','v','e' from LSB to MSB) stores corresponding data.
|
||||
|
||||
### 0x1011 - Restore default parameters
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| ARRAY | | RAM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Highest sub-index supported| UNSIGNED8 | ro | no | no | 0x04 |
|
||||
| 0x01 | Restore all default parameters| UNSIGNED32 | rw | no | no | 0x00000001 |
|
||||
| 0x02 | Restore communication default parameters| UNSIGNED32 | rw | no | no | 0x00000001 |
|
||||
| 0x03 | Restore application default parameters| UNSIGNED32 | rw | no | no | 0x00000001 |
|
||||
| 0x04 | Restore manufacturer defined default parameters| UNSIGNED32 | rw | no | no | 0x00000001 |
|
||||
|
||||
Sub-indexes 1 and above:
|
||||
* Reading provides information about its restoring capability:
|
||||
* bit 0: If set, CANopen device restores parameters
|
||||
* Writing value 0x64616F6C ('l','o','a','d' from LSB to MSB) restores corresponding data.
|
||||
|
||||
### 0x1012 - COB-ID time stamp object
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| VAR | TIME | PERSIST_COMM |
|
||||
|
||||
| Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ----------------------- | --- | --- | ---- | ------------------------------- |
|
||||
| UNSIGNED32 | rw | no | no | 0x00000100 |
|
||||
|
||||
* bit 31: If set, CANopen device consumes TIME message
|
||||
* bit 30: If set, CANopen device produces TIME message
|
||||
* bit 11-29: set to 0
|
||||
* bit 0-10: 11-bit CAN-ID
|
||||
|
||||
### 0x1014 - COB-ID EMCY
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| VAR | EM_PROD | PERSIST_COMM |
|
||||
|
||||
| Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ----------------------- | --- | --- | ---- | ------------------------------- |
|
||||
| UNSIGNED32 | rw | no | no | 0x80+$NODEID |
|
||||
|
||||
* bit 31: If set, EMCY does NOT exist / is NOT valid
|
||||
* bit 11-30: set to 0
|
||||
* bit 0-10: 11-bit CAN-ID
|
||||
|
||||
### 0x1015 - Inhibit time EMCY
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| VAR | | PERSIST_COMM |
|
||||
|
||||
| Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ----------------------- | --- | --- | ---- | ------------------------------- |
|
||||
| UNSIGNED16 | rw | no | no | 0 |
|
||||
|
||||
Inhibit time of emergency message in multiples of 100µs. The value 0 disables the inhibit time.
|
||||
|
||||
### 0x1016 - Consumer heartbeat time
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| ARRAY | HB_CONS | PERSIST_COMM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Highest sub-index supported| UNSIGNED8 | ro | no | no | 0x08 |
|
||||
| 0x01 | Consumer heartbeat time| UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x02 | Consumer heartbeat time| UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x03 | Consumer heartbeat time| UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x04 | Consumer heartbeat time| UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x05 | Consumer heartbeat time| UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x06 | Consumer heartbeat time| UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x07 | Consumer heartbeat time| UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x08 | Consumer heartbeat time| UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
|
||||
Consumer Heartbeat Time:
|
||||
* bit 24-31: set to 0
|
||||
* bit 16-23: Node ID of the monitored node. If 0 or greater than 127, sub-entry is not used.
|
||||
* bit 0-15: Heartbeat time in ms (if 0, sub-intry is not used). Value should be higher than the corresponding producer heartbeat time.
|
||||
|
||||
### 0x1017 - Producer heartbeat time
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| VAR | HB_PROD | PERSIST_COMM |
|
||||
|
||||
| Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ----------------------- | --- | --- | ---- | ------------------------------- |
|
||||
| UNSIGNED16 | rw | no | no | 0 |
|
||||
|
||||
Heartbeat producer time in ms (0 = disable transmission).
|
||||
|
||||
### 0x1018 - Identity
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| RECORD | | PERSIST_COMM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Highest sub-index supported| UNSIGNED8 | ro | no | no | 0x04 |
|
||||
| 0x01 | Vendor-ID | UNSIGNED32 | ro | no | no | 0x00000000 |
|
||||
| 0x02 | Product code | UNSIGNED32 | ro | no | no | 0x00000000 |
|
||||
| 0x03 | Revision number | UNSIGNED32 | ro | no | no | 0x00000000 |
|
||||
| 0x04 | Serial number | UNSIGNED32 | ro | no | no | 0x00000000 |
|
||||
|
||||
* Vendor-ID, assigned by CiA
|
||||
* Product code, manufacturer specific
|
||||
* Revision number:
|
||||
* bit 16-31: Major revision number (CANopen behavior has changed)
|
||||
* bit 0-15: Minor revision num. (CANopen behavior has not changed)
|
||||
* Serial number, manufacturer specific
|
||||
|
||||
### 0x1019 - Synchronous counter overflow value
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| VAR | | PERSIST_COMM |
|
||||
|
||||
| Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ----------------------- | --- | --- | ---- | ------------------------------- |
|
||||
| UNSIGNED8 | rw | no | no | 0 |
|
||||
|
||||
* Value 0: SYNC message is transmitted with data length 0.
|
||||
* Value 1: reserved.
|
||||
* Value 2-240: SYNC message has one data byte, which contains the counter.
|
||||
* Value 241-255: reserved.
|
||||
|
||||
### 0x1200 - SDO server parameter
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| RECORD | SDO_SRV | RAM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Highest sub-index supported| UNSIGNED8 | ro | no | no | 2 |
|
||||
| 0x01 | COB-ID client to server (rx)| UNSIGNED32 | ro | t | no | 0x600+$NODEID |
|
||||
| 0x02 | COB-ID server to client (tx)| UNSIGNED32 | ro | t | no | 0x580+$NODEID |
|
||||
|
||||
Sub-indexes 1 and 2:
|
||||
* bit 11-31: set to 0
|
||||
* bit 0-10: 11-bit CAN-ID
|
||||
|
||||
### 0x1280 - SDO client parameter
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| RECORD | SDO_CLI | PERSIST_COMM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Highest sub-index supported| UNSIGNED8 | ro | no | no | 0x03 |
|
||||
| 0x01 | COB-ID client to server (tx)| UNSIGNED32 | rw | tr | no | 0x80000000 |
|
||||
| 0x02 | COB-ID server to client (rx)| UNSIGNED32 | rw | tr | no | 0x80000000 |
|
||||
| 0x03 | Node-ID of the SDO server| UNSIGNED8 | rw | no | no | 0x01 |
|
||||
|
||||
* Sub-indexes 1 and 2:
|
||||
* bit 31: If set, SDO does NOT exist / is NOT valid
|
||||
* bit 30: If set, value is assigned dynamically
|
||||
* bit 11-29: set to 0
|
||||
* bit 0-10: 11-bit CAN-ID
|
||||
* Node-ID of the SDO server, 0x01 to 0x7F
|
||||
|
||||
### 0x1400 - RPDO communication parameter
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| RECORD | RPDO | PERSIST_COMM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Highest sub-index supported| UNSIGNED8 | ro | no | no | 0x05 |
|
||||
| 0x01 | COB-ID used by RPDO | UNSIGNED32 | rw | no | no | 0x80000200+$NODEID|
|
||||
| 0x02 | Transmission type | UNSIGNED8 | rw | no | no | 254 |
|
||||
| 0x05 | Event timer | UNSIGNED16 | rw | no | no | 0 |
|
||||
|
||||
* COB-ID used by RPDO:
|
||||
* bit 31: If set, PDO does not exist / is not valid
|
||||
* bit 11-30: set to 0
|
||||
* bit 0-10: 11-bit CAN-ID
|
||||
* Transmission type:
|
||||
* Value 0-240: synchronous, processed after next reception of SYNC object
|
||||
* Value 241-253: not used
|
||||
* Value 254: event-driven (manufacturer-specific)
|
||||
* Value 255: event-driven (device profile and application profile specific)
|
||||
* Event timer in ms (0 = disabled) for deadline monitoring.
|
||||
|
||||
### 0x1401 - RPDO communication parameter
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| RECORD | RPDO | PERSIST_COMM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Highest sub-index supported| UNSIGNED8 | ro | no | no | 0x05 |
|
||||
| 0x01 | COB-ID used by RPDO | UNSIGNED32 | rw | no | no | 0x80000300+$NODEID|
|
||||
| 0x02 | Transmission type | UNSIGNED8 | rw | no | no | 254 |
|
||||
| 0x05 | Event timer | UNSIGNED16 | rw | no | no | 0 |
|
||||
|
||||
* COB-ID used by RPDO:
|
||||
* bit 31: If set, PDO does not exist / is not valid
|
||||
* bit 11-30: set to 0
|
||||
* bit 0-10: 11-bit CAN-ID
|
||||
* Transmission type:
|
||||
* Value 0-240: synchronous, processed after next reception of SYNC object
|
||||
* Value 241-253: not used
|
||||
* Value 254: event-driven (manufacturer-specific)
|
||||
* Value 255: event-driven (device profile and application profile specific)
|
||||
* Event timer in ms (0 = disabled) for deadline monitoring.
|
||||
|
||||
### 0x1402 - RPDO communication parameter
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| RECORD | RPDO | PERSIST_COMM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Highest sub-index supported| UNSIGNED8 | ro | no | no | 0x05 |
|
||||
| 0x01 | COB-ID used by RPDO | UNSIGNED32 | rw | no | no | 0x80000400+$NODEID|
|
||||
| 0x02 | Transmission type | UNSIGNED8 | rw | no | no | 254 |
|
||||
| 0x05 | Event timer | UNSIGNED16 | rw | no | no | 0 |
|
||||
|
||||
* COB-ID used by RPDO:
|
||||
* bit 31: If set, PDO does not exist / is not valid
|
||||
* bit 11-30: set to 0
|
||||
* bit 0-10: 11-bit CAN-ID
|
||||
* Transmission type:
|
||||
* Value 0-240: synchronous, processed after next reception of SYNC object
|
||||
* Value 241-253: not used
|
||||
* Value 254: event-driven (manufacturer-specific)
|
||||
* Value 255: event-driven (device profile and application profile specific)
|
||||
* Event timer in ms (0 = disabled) for deadline monitoring.
|
||||
|
||||
### 0x1403 - RPDO communication parameter
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| RECORD | RPDO | PERSIST_COMM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Highest sub-index supported| UNSIGNED8 | ro | no | no | 0x05 |
|
||||
| 0x01 | COB-ID used by RPDO | UNSIGNED32 | rw | no | no | 0x80000500+$NODEID|
|
||||
| 0x02 | Transmission type | UNSIGNED8 | rw | no | no | 254 |
|
||||
| 0x05 | Event timer | UNSIGNED16 | rw | no | no | 0 |
|
||||
|
||||
* COB-ID used by RPDO:
|
||||
* bit 31: If set, PDO does not exist / is not valid
|
||||
* bit 11-30: set to 0
|
||||
* bit 0-10: 11-bit CAN-ID
|
||||
* Transmission type:
|
||||
* Value 0-240: synchronous, processed after next reception of SYNC object
|
||||
* Value 241-253: not used
|
||||
* Value 254: event-driven (manufacturer-specific)
|
||||
* Value 255: event-driven (device profile and application profile specific)
|
||||
* Event timer in ms (0 = disabled) for deadline monitoring.
|
||||
|
||||
### 0x1600 - RPDO mapping parameter
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| RECORD | | PERSIST_COMM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Number of mapped application objects in PDO| UNSIGNED8 | rw | no | no | 0 |
|
||||
| 0x01 | Application object 1 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x02 | Application object 2 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x03 | Application object 3 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x04 | Application object 4 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x05 | Application object 5 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x06 | Application object 6 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x07 | Application object 7 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x08 | Application object 8 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
|
||||
* Number of mapped application objects in PDO:
|
||||
* Value 0: mapping is disabled.
|
||||
* Value 1: sub-index 0x01 is valid.
|
||||
* Value 2-8: sub-indexes 0x01 to (0x02 to 0x08) are valid.
|
||||
* Application object 1-8:
|
||||
* bit 16-31: index
|
||||
* bit 8-15: sub-index
|
||||
* bit 0-7: data length in bits
|
||||
|
||||
### 0x1601 - RPDO mapping parameter
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| RECORD | | PERSIST_COMM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Number of mapped application objects in PDO| UNSIGNED8 | rw | no | no | 0 |
|
||||
| 0x01 | Application object 1 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x02 | Application object 2 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x03 | Application object 3 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x04 | Application object 4 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x05 | Application object 5 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x06 | Application object 6 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x07 | Application object 7 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x08 | Application object 8 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
|
||||
* Number of mapped application objects in PDO:
|
||||
* Value 0: mapping is disabled.
|
||||
* Value 1: sub-index 0x01 is valid.
|
||||
* Value 2-8: sub-indexes 0x01 to (0x02 to 0x08) are valid.
|
||||
* Application object 1-8:
|
||||
* bit 16-31: index
|
||||
* bit 8-15: sub-index
|
||||
* bit 0-7: data length in bits
|
||||
|
||||
### 0x1602 - RPDO mapping parameter
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| RECORD | | PERSIST_COMM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Number of mapped application objects in PDO| UNSIGNED8 | rw | no | no | 0 |
|
||||
| 0x01 | Application object 1 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x02 | Application object 2 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x03 | Application object 3 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x04 | Application object 4 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x05 | Application object 5 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x06 | Application object 6 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x07 | Application object 7 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x08 | Application object 8 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
|
||||
* Number of mapped application objects in PDO:
|
||||
* Value 0: mapping is disabled.
|
||||
* Value 1: sub-index 0x01 is valid.
|
||||
* Value 2-8: sub-indexes 0x01 to (0x02 to 0x08) are valid.
|
||||
* Application object 1-8:
|
||||
* bit 16-31: index
|
||||
* bit 8-15: sub-index
|
||||
* bit 0-7: data length in bits
|
||||
|
||||
### 0x1603 - RPDO mapping parameter
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| RECORD | | PERSIST_COMM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Number of mapped application objects in PDO| UNSIGNED8 | rw | no | no | 0 |
|
||||
| 0x01 | Application object 1 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x02 | Application object 2 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x03 | Application object 3 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x04 | Application object 4 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x05 | Application object 5 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x06 | Application object 6 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x07 | Application object 7 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x08 | Application object 8 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
|
||||
* Number of mapped application objects in PDO:
|
||||
* Value 0: mapping is disabled.
|
||||
* Value 1: sub-index 0x01 is valid.
|
||||
* Value 2-8: sub-indexes 0x01 to (0x02 to 0x08) are valid.
|
||||
* Application object 1-8:
|
||||
* bit 16-31: index
|
||||
* bit 8-15: sub-index
|
||||
* bit 0-7: data length in bits
|
||||
|
||||
### 0x1800 - TPDO communication parameter
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| RECORD | TPDO | PERSIST_COMM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Highest sub-index supported| UNSIGNED8 | ro | no | no | 0x06 |
|
||||
| 0x01 | COB-ID used by TPDO | UNSIGNED32 | rw | no | no | 0xC0000180+$NODEID|
|
||||
| 0x02 | Transmission type | UNSIGNED8 | rw | no | no | 254 |
|
||||
| 0x03 | Inhibit time | UNSIGNED16 | rw | no | no | 0 |
|
||||
| 0x05 | Event timer | UNSIGNED16 | rw | no | no | 0 |
|
||||
| 0x06 | SYNC start value | UNSIGNED8 | rw | no | no | 0 |
|
||||
|
||||
* COB-ID used by RPDO:
|
||||
* bit 31: If set, PDO does not exist / is not valid
|
||||
* bit 30: If set, NO RTR is allowed on this PDO
|
||||
* bit 11-29: set to 0
|
||||
* bit 0-10: 11-bit CAN-ID
|
||||
* Transmission type:
|
||||
* Value 0: synchronous (acyclic)
|
||||
* Value 1-240: synchronous (cyclic every (1-240)-th sync)
|
||||
* Value 241-253: not used
|
||||
* Value 254: event-driven (manufacturer-specific)
|
||||
* Value 255: event-driven (device profile and application profile specific)
|
||||
* Inhibit time in multiple of 100µs, if the transmission type is set to 254 or 255 (0 = disabled).
|
||||
* Event timer interval in ms, if the transmission type is set to 254 or 255 (0 = disabled).
|
||||
* SYNC start value
|
||||
* Value 0: Counter of the SYNC message shall not be processed.
|
||||
* Value 1-240: The SYNC message with the counter value equal to this value shall be regarded as the first received SYNC message.
|
||||
|
||||
### 0x1801 - TPDO communication parameter
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| RECORD | TPDO | PERSIST_COMM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Highest sub-index supported| UNSIGNED8 | ro | no | no | 0x06 |
|
||||
| 0x01 | COB-ID used by TPDO | UNSIGNED32 | rw | no | no | 0xC0000280+$NODEID|
|
||||
| 0x02 | Transmission type | UNSIGNED8 | rw | no | no | 254 |
|
||||
| 0x03 | Inhibit time | UNSIGNED16 | rw | no | no | 0 |
|
||||
| 0x05 | Event timer | UNSIGNED16 | rw | no | no | 0 |
|
||||
| 0x06 | SYNC start value | UNSIGNED8 | rw | no | no | 0 |
|
||||
|
||||
* COB-ID used by RPDO:
|
||||
* bit 31: If set, PDO does not exist / is not valid
|
||||
* bit 30: If set, NO RTR is allowed on this PDO
|
||||
* bit 11-29: set to 0
|
||||
* bit 0-10: 11-bit CAN-ID
|
||||
* Transmission type:
|
||||
* Value 0: synchronous (acyclic)
|
||||
* Value 1-240: synchronous (cyclic every (1-240)-th sync)
|
||||
* Value 241-253: not used
|
||||
* Value 254: event-driven (manufacturer-specific)
|
||||
* Value 255: event-driven (device profile and application profile specific)
|
||||
* Inhibit time in multiple of 100µs, if the transmission type is set to 254 or 255 (0 = disabled).
|
||||
* Event timer interval in ms, if the transmission type is set to 254 or 255 (0 = disabled).
|
||||
* SYNC start value
|
||||
* Value 0: Counter of the SYNC message shall not be processed.
|
||||
* Value 1-240: The SYNC message with the counter value equal to this value shall be regarded as the first received SYNC message.
|
||||
|
||||
### 0x1802 - TPDO communication parameter
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| RECORD | TPDO | PERSIST_COMM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Highest sub-index supported| UNSIGNED8 | ro | no | no | 0x06 |
|
||||
| 0x01 | COB-ID used by TPDO | UNSIGNED32 | rw | no | no | 0xC0000380+$NODEID|
|
||||
| 0x02 | Transmission type | UNSIGNED8 | rw | no | no | 254 |
|
||||
| 0x03 | Inhibit time | UNSIGNED16 | rw | no | no | 0 |
|
||||
| 0x05 | Event timer | UNSIGNED16 | rw | no | no | 0 |
|
||||
| 0x06 | SYNC start value | UNSIGNED8 | rw | no | no | 0 |
|
||||
|
||||
* COB-ID used by RPDO:
|
||||
* bit 31: If set, PDO does not exist / is not valid
|
||||
* bit 30: If set, NO RTR is allowed on this PDO
|
||||
* bit 11-29: set to 0
|
||||
* bit 0-10: 11-bit CAN-ID
|
||||
* Transmission type:
|
||||
* Value 0: synchronous (acyclic)
|
||||
* Value 1-240: synchronous (cyclic every (1-240)-th sync)
|
||||
* Value 241-253: not used
|
||||
* Value 254: event-driven (manufacturer-specific)
|
||||
* Value 255: event-driven (device profile and application profile specific)
|
||||
* Inhibit time in multiple of 100µs, if the transmission type is set to 254 or 255 (0 = disabled).
|
||||
* Event timer interval in ms, if the transmission type is set to 254 or 255 (0 = disabled).
|
||||
* SYNC start value
|
||||
* Value 0: Counter of the SYNC message shall not be processed.
|
||||
* Value 1-240: The SYNC message with the counter value equal to this value shall be regarded as the first received SYNC message.
|
||||
|
||||
### 0x1803 - TPDO communication parameter
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| RECORD | TPDO | PERSIST_COMM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Highest sub-index supported| UNSIGNED8 | ro | no | no | 0x06 |
|
||||
| 0x01 | COB-ID used by TPDO | UNSIGNED32 | rw | no | no | 0xC0000480+$NODEID|
|
||||
| 0x02 | Transmission type | UNSIGNED8 | rw | no | no | 254 |
|
||||
| 0x03 | Inhibit time | UNSIGNED16 | rw | no | no | 0 |
|
||||
| 0x05 | Event timer | UNSIGNED16 | rw | no | no | 0 |
|
||||
| 0x06 | SYNC start value | UNSIGNED8 | rw | no | no | 0 |
|
||||
|
||||
* COB-ID used by RPDO:
|
||||
* bit 31: If set, PDO does not exist / is not valid
|
||||
* bit 30: If set, NO RTR is allowed on this PDO
|
||||
* bit 11-29: set to 0
|
||||
* bit 0-10: 11-bit CAN-ID
|
||||
* Transmission type:
|
||||
* Value 0: synchronous (acyclic)
|
||||
* Value 1-240: synchronous (cyclic every (1-240)-th sync)
|
||||
* Value 241-253: not used
|
||||
* Value 254: event-driven (manufacturer-specific)
|
||||
* Value 255: event-driven (device profile and application profile specific)
|
||||
* Inhibit time in multiple of 100µs, if the transmission type is set to 254 or 255 (0 = disabled).
|
||||
* Event timer interval in ms, if the transmission type is set to 254 or 255 (0 = disabled).
|
||||
* SYNC start value
|
||||
* Value 0: Counter of the SYNC message shall not be processed.
|
||||
* Value 1-240: The SYNC message with the counter value equal to this value shall be regarded as the first received SYNC message.
|
||||
|
||||
### 0x1A00 - TPDO mapping parameter
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| RECORD | | PERSIST_COMM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Number of mapped application objects in PDO| UNSIGNED8 | rw | no | no | 0 |
|
||||
| 0x01 | Application object 1 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x02 | Application object 2 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x03 | Application object 3 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x04 | Application object 4 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x05 | Application object 5 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x06 | Application object 6 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x07 | Application object 7 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x08 | Application object 8 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
|
||||
* Number of mapped application objects in PDO:
|
||||
* Value 0: mapping is disabled.
|
||||
* Value 1: sub-index 0x01 is valid.
|
||||
* Value 2-8: sub-indexes 0x01 to (0x02 to 0x08) are valid.
|
||||
* Application object 1-8:
|
||||
* bit 16-31: index
|
||||
* bit 8-15: sub-index
|
||||
* bit 0-7: data length in bits
|
||||
|
||||
### 0x1A01 - TPDO mapping parameter
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| RECORD | | PERSIST_COMM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Number of mapped application objects in PDO| UNSIGNED8 | rw | no | no | 0 |
|
||||
| 0x01 | Application object 1 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x02 | Application object 2 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x03 | Application object 3 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x04 | Application object 4 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x05 | Application object 5 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x06 | Application object 6 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x07 | Application object 7 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x08 | Application object 8 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
|
||||
* Number of mapped application objects in PDO:
|
||||
* Value 0: mapping is disabled.
|
||||
* Value 1: sub-index 0x01 is valid.
|
||||
* Value 2-8: sub-indexes 0x01 to (0x02 to 0x08) are valid.
|
||||
* Application object 1-8:
|
||||
* bit 16-31: index
|
||||
* bit 8-15: sub-index
|
||||
* bit 0-7: data length in bits
|
||||
|
||||
### 0x1A02 - TPDO mapping parameter
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| RECORD | | PERSIST_COMM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Number of mapped application objects in PDO| UNSIGNED8 | rw | no | no | 0 |
|
||||
| 0x01 | Application object 1 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x02 | Application object 2 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x03 | Application object 3 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x04 | Application object 4 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x05 | Application object 5 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x06 | Application object 6 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x07 | Application object 7 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x08 | Application object 8 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
|
||||
* Number of mapped application objects in PDO:
|
||||
* Value 0: mapping is disabled.
|
||||
* Value 1: sub-index 0x01 is valid.
|
||||
* Value 2-8: sub-indexes 0x01 to (0x02 to 0x08) are valid.
|
||||
* Application object 1-8:
|
||||
* bit 16-31: index
|
||||
* bit 8-15: sub-index
|
||||
* bit 0-7: data length in bits
|
||||
|
||||
### 0x1A03 - TPDO mapping parameter
|
||||
| Object Type | Count Label | Storage Group |
|
||||
| ----------- | -------------- | -------------- |
|
||||
| RECORD | | PERSIST_COMM |
|
||||
|
||||
| Sub | Name | Data Type | SDO | PDO | SRDO | Default Value |
|
||||
| ---- | --------------------- | ---------- | --- | --- | ---- | ------------- |
|
||||
| 0x00 | Number of mapped application objects in PDO| UNSIGNED8 | rw | no | no | 0 |
|
||||
| 0x01 | Application object 1 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x02 | Application object 2 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x03 | Application object 3 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x04 | Application object 4 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x05 | Application object 5 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x06 | Application object 6 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x07 | Application object 7 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
| 0x08 | Application object 8 | UNSIGNED32 | rw | no | no | 0x00000000 |
|
||||
|
||||
* Number of mapped application objects in PDO:
|
||||
* Value 0: mapping is disabled.
|
||||
* Value 1: sub-index 0x01 is valid.
|
||||
* Value 2-8: sub-indexes 0x01 to (0x02 to 0x08) are valid.
|
||||
* Application object 1-8:
|
||||
* bit 16-31: index
|
||||
* bit 8-15: sub-index
|
||||
* bit 0-7: data length in bits
|
||||
1085
Middleware/CANopenNode/CO_driver_STM32/DS301_profile.xpd
Normal file
1085
Middleware/CANopenNode/CO_driver_STM32/DS301_profile.xpd
Normal file
File diff suppressed because it is too large
Load Diff
58
Middleware/CANopenNode/CO_driver_STM32/Makefile
Normal file
58
Middleware/CANopenNode/CO_driver_STM32/Makefile
Normal file
@@ -0,0 +1,58 @@
|
||||
# Makefile for CANopenNode, basic compile with blank CAN device
|
||||
|
||||
|
||||
DRV_SRC = .
|
||||
CANOPEN_SRC = ..
|
||||
APPL_SRC = .
|
||||
|
||||
|
||||
LINK_TARGET = canopennode_blank
|
||||
|
||||
|
||||
INCLUDE_DIRS = \
|
||||
-I$(DRV_SRC) \
|
||||
-I$(CANOPEN_SRC) \
|
||||
-I$(APPL_SRC)
|
||||
|
||||
|
||||
SOURCES = \
|
||||
$(DRV_SRC)/CO_driver_blank.c \
|
||||
$(DRV_SRC)/CO_storageBlank.c \
|
||||
$(CANOPEN_SRC)/301/CO_ODinterface.c \
|
||||
$(CANOPEN_SRC)/301/CO_NMT_Heartbeat.c \
|
||||
$(CANOPEN_SRC)/301/CO_HBconsumer.c \
|
||||
$(CANOPEN_SRC)/301/CO_Emergency.c \
|
||||
$(CANOPEN_SRC)/301/CO_SDOserver.c \
|
||||
$(CANOPEN_SRC)/301/CO_TIME.c \
|
||||
$(CANOPEN_SRC)/301/CO_SYNC.c \
|
||||
$(CANOPEN_SRC)/301/CO_PDO.c \
|
||||
$(CANOPEN_SRC)/303/CO_LEDs.c \
|
||||
$(CANOPEN_SRC)/305/CO_LSSslave.c \
|
||||
$(CANOPEN_SRC)/storage/CO_storage.c \
|
||||
$(CANOPEN_SRC)/CANopen.c \
|
||||
$(APPL_SRC)/OD.c \
|
||||
$(DRV_SRC)/main_blank.c
|
||||
|
||||
|
||||
OBJS = $(SOURCES:%.c=%.o)
|
||||
CC ?= gcc
|
||||
OPT =
|
||||
OPT += -g
|
||||
#OPT += -DCO_USE_GLOBALS
|
||||
#OPT += -DCO_MULTIPLE_OD
|
||||
CFLAGS = -Wall $(OPT) $(INCLUDE_DIRS)
|
||||
LDFLAGS =
|
||||
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: clean $(LINK_TARGET)
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(LINK_TARGET)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(LINK_TARGET): $(OBJS)
|
||||
$(CC) $(LDFLAGS) $^ -o $@
|
||||
1249
Middleware/CANopenNode/CO_driver_STM32/OD.c
Normal file
1249
Middleware/CANopenNode/CO_driver_STM32/OD.c
Normal file
File diff suppressed because it is too large
Load Diff
420
Middleware/CANopenNode/CO_driver_STM32/OD.h
Normal file
420
Middleware/CANopenNode/CO_driver_STM32/OD.h
Normal file
@@ -0,0 +1,420 @@
|
||||
/*******************************************************************************
|
||||
CANopen Object Dictionary definition for CANopenNode V4
|
||||
|
||||
This file was automatically generated by CANopenEditor v4.2.3-0-gc1071ab+c1071ab3197f9bbf718123ec5bbabf449b2f7bab
|
||||
|
||||
https://github.com/CANopenNode/CANopenNode
|
||||
https://github.com/CANopenNode/CANopenEditor
|
||||
|
||||
DON'T EDIT THIS FILE MANUALLY !!!!
|
||||
********************************************************************************
|
||||
|
||||
File info:
|
||||
File Names: OD.h; OD.c
|
||||
Project File: NodeSlave.xdd
|
||||
File Version: 1
|
||||
|
||||
Created: 2026/1/30 14:35:40
|
||||
Created By:
|
||||
Modified: 2026/2/26 17:17:54
|
||||
Modified By:
|
||||
|
||||
Device Info:
|
||||
Vendor Name:
|
||||
Vendor ID:
|
||||
Product Name: NodeSlave
|
||||
Product ID:
|
||||
|
||||
Description:
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef OD_H
|
||||
#define OD_H
|
||||
/*******************************************************************************
|
||||
Counters of OD objects
|
||||
*******************************************************************************/
|
||||
#define OD_CNT_NMT 1
|
||||
#define OD_CNT_EM 1
|
||||
#define OD_CNT_SYNC 1
|
||||
#define OD_CNT_SYNC_PROD 1
|
||||
#define OD_CNT_STORAGE 1
|
||||
#define OD_CNT_TIME 1
|
||||
#define OD_CNT_EM_PROD 1
|
||||
#define OD_CNT_HB_CONS 1
|
||||
#define OD_CNT_HB_PROD 1
|
||||
#define OD_CNT_SDO_SRV 1
|
||||
#define OD_CNT_SDO_CLI 1
|
||||
#define OD_CNT_RPDO 4
|
||||
#define OD_CNT_TPDO 4
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
Sizes of OD arrays
|
||||
*******************************************************************************/
|
||||
#define OD_CNT_ARR_1003 16
|
||||
#define OD_CNT_ARR_1010 4
|
||||
#define OD_CNT_ARR_1011 4
|
||||
#define OD_CNT_ARR_1016 8
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
OD data declaration of all groups
|
||||
*******************************************************************************/
|
||||
typedef struct {
|
||||
uint32_t x1000_deviceType;
|
||||
uint32_t x1005_COB_ID_SYNCMessage;
|
||||
uint32_t x1006_communicationCyclePeriod;
|
||||
uint32_t x1007_synchronousWindowLength;
|
||||
uint32_t x1012_COB_IDTimeStampObject;
|
||||
uint32_t x1014_COB_ID_EMCY;
|
||||
uint16_t x1015_inhibitTimeEMCY;
|
||||
uint8_t x1016_consumerHeartbeatTime_sub0;
|
||||
uint32_t x1016_consumerHeartbeatTime[OD_CNT_ARR_1016];
|
||||
uint16_t x1017_producerHeartbeatTime;
|
||||
struct {
|
||||
uint8_t highestSub_indexSupported;
|
||||
uint32_t vendor_ID;
|
||||
uint32_t productCode;
|
||||
uint32_t revisionNumber;
|
||||
uint32_t serialNumber;
|
||||
} x1018_identity;
|
||||
uint8_t x1019_synchronousCounterOverflowValue;
|
||||
struct {
|
||||
uint8_t highestSub_indexSupported;
|
||||
uint32_t COB_IDClientToServerTx;
|
||||
uint32_t COB_IDServerToClientRx;
|
||||
uint8_t node_IDOfTheSDOServer;
|
||||
} x1280_SDOClientParameter;
|
||||
struct {
|
||||
uint8_t highestSub_indexSupported;
|
||||
uint32_t COB_IDUsedByRPDO;
|
||||
uint8_t transmissionType;
|
||||
uint16_t eventTimer;
|
||||
} x1400_RPDOCommunicationParameter;
|
||||
struct {
|
||||
uint8_t highestSub_indexSupported;
|
||||
uint32_t COB_IDUsedByRPDO;
|
||||
uint8_t transmissionType;
|
||||
uint16_t eventTimer;
|
||||
} x1401_RPDOCommunicationParameter;
|
||||
struct {
|
||||
uint8_t highestSub_indexSupported;
|
||||
uint32_t COB_IDUsedByRPDO;
|
||||
uint8_t transmissionType;
|
||||
uint16_t eventTimer;
|
||||
} x1402_RPDOCommunicationParameter;
|
||||
struct {
|
||||
uint8_t highestSub_indexSupported;
|
||||
uint32_t COB_IDUsedByRPDO;
|
||||
uint8_t transmissionType;
|
||||
uint16_t eventTimer;
|
||||
} x1403_RPDOCommunicationParameter;
|
||||
struct {
|
||||
uint8_t numberOfMappedApplicationObjectsInPDO;
|
||||
uint32_t applicationObject1;
|
||||
uint32_t applicationObject2;
|
||||
uint32_t applicationObject3;
|
||||
uint32_t applicationObject4;
|
||||
uint32_t applicationObject5;
|
||||
uint32_t applicationObject6;
|
||||
uint32_t applicationObject7;
|
||||
uint32_t applicationObject8;
|
||||
} x1600_RPDOMappingParameter;
|
||||
struct {
|
||||
uint8_t numberOfMappedApplicationObjectsInPDO;
|
||||
uint32_t applicationObject1;
|
||||
uint32_t applicationObject2;
|
||||
uint32_t applicationObject3;
|
||||
uint32_t applicationObject4;
|
||||
uint32_t applicationObject5;
|
||||
uint32_t applicationObject6;
|
||||
uint32_t applicationObject7;
|
||||
uint32_t applicationObject8;
|
||||
} x1601_RPDOMappingParameter;
|
||||
struct {
|
||||
uint8_t numberOfMappedApplicationObjectsInPDO;
|
||||
uint32_t applicationObject1;
|
||||
uint32_t applicationObject2;
|
||||
uint32_t applicationObject3;
|
||||
uint32_t applicationObject4;
|
||||
uint32_t applicationObject5;
|
||||
uint32_t applicationObject6;
|
||||
uint32_t applicationObject7;
|
||||
uint32_t applicationObject8;
|
||||
} x1602_RPDOMappingParameter;
|
||||
struct {
|
||||
uint8_t numberOfMappedApplicationObjectsInPDO;
|
||||
uint32_t applicationObject1;
|
||||
uint32_t applicationObject2;
|
||||
uint32_t applicationObject3;
|
||||
uint32_t applicationObject4;
|
||||
uint32_t applicationObject5;
|
||||
uint32_t applicationObject6;
|
||||
uint32_t applicationObject7;
|
||||
uint32_t applicationObject8;
|
||||
} x1603_RPDOMappingParameter;
|
||||
struct {
|
||||
uint8_t highestSub_indexSupported;
|
||||
uint32_t COB_IDUsedByTPDO;
|
||||
uint8_t transmissionType;
|
||||
uint16_t inhibitTime;
|
||||
uint16_t eventTimer;
|
||||
uint8_t SYNCStartValue;
|
||||
} x1800_TPDOCommunicationParameter;
|
||||
struct {
|
||||
uint8_t highestSub_indexSupported;
|
||||
uint32_t COB_IDUsedByTPDO;
|
||||
uint8_t transmissionType;
|
||||
uint16_t inhibitTime;
|
||||
uint16_t eventTimer;
|
||||
uint8_t SYNCStartValue;
|
||||
} x1801_TPDOCommunicationParameter;
|
||||
struct {
|
||||
uint8_t highestSub_indexSupported;
|
||||
uint32_t COB_IDUsedByTPDO;
|
||||
uint8_t transmissionType;
|
||||
uint16_t inhibitTime;
|
||||
uint16_t eventTimer;
|
||||
uint8_t SYNCStartValue;
|
||||
} x1802_TPDOCommunicationParameter;
|
||||
struct {
|
||||
uint8_t highestSub_indexSupported;
|
||||
uint32_t COB_IDUsedByTPDO;
|
||||
uint8_t transmissionType;
|
||||
uint16_t inhibitTime;
|
||||
uint16_t eventTimer;
|
||||
uint8_t SYNCStartValue;
|
||||
} x1803_TPDOCommunicationParameter;
|
||||
struct {
|
||||
uint8_t numberOfMappedApplicationObjectsInPDO;
|
||||
uint32_t applicationObject1;
|
||||
uint32_t applicationObject2;
|
||||
uint32_t applicationObject3;
|
||||
uint32_t applicationObject4;
|
||||
uint32_t applicationObject5;
|
||||
uint32_t applicationObject6;
|
||||
uint32_t applicationObject7;
|
||||
uint32_t applicationObject8;
|
||||
} x1A00_TPDOMappingParameter;
|
||||
struct {
|
||||
uint8_t numberOfMappedApplicationObjectsInPDO;
|
||||
uint32_t applicationObject1;
|
||||
uint32_t applicationObject2;
|
||||
uint32_t applicationObject3;
|
||||
uint32_t applicationObject4;
|
||||
uint32_t applicationObject5;
|
||||
uint32_t applicationObject6;
|
||||
uint32_t applicationObject7;
|
||||
uint32_t applicationObject8;
|
||||
} x1A01_TPDOMappingParameter;
|
||||
struct {
|
||||
uint8_t numberOfMappedApplicationObjectsInPDO;
|
||||
uint32_t applicationObject1;
|
||||
uint32_t applicationObject2;
|
||||
uint32_t applicationObject3;
|
||||
uint32_t applicationObject4;
|
||||
uint32_t applicationObject5;
|
||||
uint32_t applicationObject6;
|
||||
uint32_t applicationObject7;
|
||||
uint32_t applicationObject8;
|
||||
} x1A02_TPDOMappingParameter;
|
||||
struct {
|
||||
uint8_t numberOfMappedApplicationObjectsInPDO;
|
||||
uint32_t applicationObject1;
|
||||
uint32_t applicationObject2;
|
||||
uint32_t applicationObject3;
|
||||
uint32_t applicationObject4;
|
||||
uint32_t applicationObject5;
|
||||
uint32_t applicationObject6;
|
||||
uint32_t applicationObject7;
|
||||
uint32_t applicationObject8;
|
||||
} x1A03_TPDOMappingParameter;
|
||||
} OD_PERSIST_COMM_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t x1001_errorRegister;
|
||||
uint8_t x1010_storeParameters_sub0;
|
||||
uint32_t x1010_storeParameters[OD_CNT_ARR_1010];
|
||||
uint8_t x1011_restoreDefaultParameters_sub0;
|
||||
uint32_t x1011_restoreDefaultParameters[OD_CNT_ARR_1011];
|
||||
struct {
|
||||
uint8_t highestSub_indexSupported;
|
||||
uint32_t COB_IDClientToServerRx;
|
||||
uint32_t COB_IDServerToClientTx;
|
||||
} x1200_SDOServerParameter;
|
||||
uint16_t x6040_controlword;
|
||||
uint16_t x6041_statusword;
|
||||
int8_t x6060_modesOfOperation;
|
||||
int8_t x6061_modesOfOperationDisplay;
|
||||
int32_t x6064_positionActualValue;
|
||||
float32_t x6065_followingErrorWindow;
|
||||
int32_t x606C_velocityActualValue;
|
||||
uint32_t x607A_targetPosition;
|
||||
uint32_t x6083_acceleration;
|
||||
uint32_t x6084_deceleration;
|
||||
int32_t x60FF_targetVelocity;
|
||||
} OD_RAM_t;
|
||||
|
||||
#ifndef OD_ATTR_PERSIST_COMM
|
||||
#define OD_ATTR_PERSIST_COMM
|
||||
#endif
|
||||
extern OD_ATTR_PERSIST_COMM OD_PERSIST_COMM_t OD_PERSIST_COMM;
|
||||
|
||||
#ifndef OD_ATTR_RAM
|
||||
#define OD_ATTR_RAM
|
||||
#endif
|
||||
extern OD_ATTR_RAM OD_RAM_t OD_RAM;
|
||||
|
||||
#ifndef OD_ATTR_OD
|
||||
#define OD_ATTR_OD
|
||||
#endif
|
||||
extern OD_ATTR_OD OD_t *OD;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
Object dictionary entries - shortcuts
|
||||
*******************************************************************************/
|
||||
#define OD_ENTRY_H1000 &OD->list[0]
|
||||
#define OD_ENTRY_H1001 &OD->list[1]
|
||||
#define OD_ENTRY_H1003 &OD->list[2]
|
||||
#define OD_ENTRY_H1005 &OD->list[3]
|
||||
#define OD_ENTRY_H1006 &OD->list[4]
|
||||
#define OD_ENTRY_H1007 &OD->list[5]
|
||||
#define OD_ENTRY_H1010 &OD->list[6]
|
||||
#define OD_ENTRY_H1011 &OD->list[7]
|
||||
#define OD_ENTRY_H1012 &OD->list[8]
|
||||
#define OD_ENTRY_H1014 &OD->list[9]
|
||||
#define OD_ENTRY_H1015 &OD->list[10]
|
||||
#define OD_ENTRY_H1016 &OD->list[11]
|
||||
#define OD_ENTRY_H1017 &OD->list[12]
|
||||
#define OD_ENTRY_H1018 &OD->list[13]
|
||||
#define OD_ENTRY_H1019 &OD->list[14]
|
||||
#define OD_ENTRY_H1200 &OD->list[15]
|
||||
#define OD_ENTRY_H1280 &OD->list[16]
|
||||
#define OD_ENTRY_H1400 &OD->list[17]
|
||||
#define OD_ENTRY_H1401 &OD->list[18]
|
||||
#define OD_ENTRY_H1402 &OD->list[19]
|
||||
#define OD_ENTRY_H1403 &OD->list[20]
|
||||
#define OD_ENTRY_H1600 &OD->list[21]
|
||||
#define OD_ENTRY_H1601 &OD->list[22]
|
||||
#define OD_ENTRY_H1602 &OD->list[23]
|
||||
#define OD_ENTRY_H1603 &OD->list[24]
|
||||
#define OD_ENTRY_H1800 &OD->list[25]
|
||||
#define OD_ENTRY_H1801 &OD->list[26]
|
||||
#define OD_ENTRY_H1802 &OD->list[27]
|
||||
#define OD_ENTRY_H1803 &OD->list[28]
|
||||
#define OD_ENTRY_H1A00 &OD->list[29]
|
||||
#define OD_ENTRY_H1A01 &OD->list[30]
|
||||
#define OD_ENTRY_H1A02 &OD->list[31]
|
||||
#define OD_ENTRY_H1A03 &OD->list[32]
|
||||
#define OD_ENTRY_H6040 &OD->list[33]
|
||||
#define OD_ENTRY_H6041 &OD->list[34]
|
||||
#define OD_ENTRY_H6060 &OD->list[35]
|
||||
#define OD_ENTRY_H6061 &OD->list[36]
|
||||
#define OD_ENTRY_H6064 &OD->list[37]
|
||||
#define OD_ENTRY_H6065 &OD->list[38]
|
||||
#define OD_ENTRY_H606C &OD->list[39]
|
||||
#define OD_ENTRY_H607A &OD->list[40]
|
||||
#define OD_ENTRY_H6083 &OD->list[41]
|
||||
#define OD_ENTRY_H6084 &OD->list[42]
|
||||
#define OD_ENTRY_H60FF &OD->list[43]
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
Object dictionary entries - shortcuts with names
|
||||
*******************************************************************************/
|
||||
#define OD_ENTRY_H1000_deviceType &OD->list[0]
|
||||
#define OD_ENTRY_H1001_errorRegister &OD->list[1]
|
||||
#define OD_ENTRY_H1003_pre_definedErrorField &OD->list[2]
|
||||
#define OD_ENTRY_H1005_COB_ID_SYNCMessage &OD->list[3]
|
||||
#define OD_ENTRY_H1006_communicationCyclePeriod &OD->list[4]
|
||||
#define OD_ENTRY_H1007_synchronousWindowLength &OD->list[5]
|
||||
#define OD_ENTRY_H1010_storeParameters &OD->list[6]
|
||||
#define OD_ENTRY_H1011_restoreDefaultParameters &OD->list[7]
|
||||
#define OD_ENTRY_H1012_COB_IDTimeStampObject &OD->list[8]
|
||||
#define OD_ENTRY_H1014_COB_ID_EMCY &OD->list[9]
|
||||
#define OD_ENTRY_H1015_inhibitTimeEMCY &OD->list[10]
|
||||
#define OD_ENTRY_H1016_consumerHeartbeatTime &OD->list[11]
|
||||
#define OD_ENTRY_H1017_producerHeartbeatTime &OD->list[12]
|
||||
#define OD_ENTRY_H1018_identity &OD->list[13]
|
||||
#define OD_ENTRY_H1019_synchronousCounterOverflowValue &OD->list[14]
|
||||
#define OD_ENTRY_H1200_SDOServerParameter &OD->list[15]
|
||||
#define OD_ENTRY_H1280_SDOClientParameter &OD->list[16]
|
||||
#define OD_ENTRY_H1400_RPDOCommunicationParameter &OD->list[17]
|
||||
#define OD_ENTRY_H1401_RPDOCommunicationParameter &OD->list[18]
|
||||
#define OD_ENTRY_H1402_RPDOCommunicationParameter &OD->list[19]
|
||||
#define OD_ENTRY_H1403_RPDOCommunicationParameter &OD->list[20]
|
||||
#define OD_ENTRY_H1600_RPDOMappingParameter &OD->list[21]
|
||||
#define OD_ENTRY_H1601_RPDOMappingParameter &OD->list[22]
|
||||
#define OD_ENTRY_H1602_RPDOMappingParameter &OD->list[23]
|
||||
#define OD_ENTRY_H1603_RPDOMappingParameter &OD->list[24]
|
||||
#define OD_ENTRY_H1800_TPDOCommunicationParameter &OD->list[25]
|
||||
#define OD_ENTRY_H1801_TPDOCommunicationParameter &OD->list[26]
|
||||
#define OD_ENTRY_H1802_TPDOCommunicationParameter &OD->list[27]
|
||||
#define OD_ENTRY_H1803_TPDOCommunicationParameter &OD->list[28]
|
||||
#define OD_ENTRY_H1A00_TPDOMappingParameter &OD->list[29]
|
||||
#define OD_ENTRY_H1A01_TPDOMappingParameter &OD->list[30]
|
||||
#define OD_ENTRY_H1A02_TPDOMappingParameter &OD->list[31]
|
||||
#define OD_ENTRY_H1A03_TPDOMappingParameter &OD->list[32]
|
||||
#define OD_ENTRY_H6040_controlword &OD->list[33]
|
||||
#define OD_ENTRY_H6041_statusword &OD->list[34]
|
||||
#define OD_ENTRY_H6060_modesOfOperation &OD->list[35]
|
||||
#define OD_ENTRY_H6061_modesOfOperationDisplay &OD->list[36]
|
||||
#define OD_ENTRY_H6064_positionActualValue &OD->list[37]
|
||||
#define OD_ENTRY_H6065_followingErrorWindow &OD->list[38]
|
||||
#define OD_ENTRY_H606C_velocityActualValue &OD->list[39]
|
||||
#define OD_ENTRY_H607A_targetPosition &OD->list[40]
|
||||
#define OD_ENTRY_H6083_acceleration &OD->list[41]
|
||||
#define OD_ENTRY_H6084_deceleration &OD->list[42]
|
||||
#define OD_ENTRY_H60FF_targetVelocity &OD->list[43]
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
OD config structure
|
||||
*******************************************************************************/
|
||||
#ifdef CO_MULTIPLE_OD
|
||||
#define OD_INIT_CONFIG(config) {\
|
||||
(config).CNT_NMT = OD_CNT_NMT;\
|
||||
(config).ENTRY_H1017 = OD_ENTRY_H1017;\
|
||||
(config).CNT_HB_CONS = OD_CNT_HB_CONS;\
|
||||
(config).CNT_ARR_1016 = OD_CNT_ARR_1016;\
|
||||
(config).ENTRY_H1016 = OD_ENTRY_H1016;\
|
||||
(config).CNT_EM = OD_CNT_EM;\
|
||||
(config).ENTRY_H1001 = OD_ENTRY_H1001;\
|
||||
(config).ENTRY_H1014 = OD_ENTRY_H1014;\
|
||||
(config).ENTRY_H1015 = OD_ENTRY_H1015;\
|
||||
(config).CNT_ARR_1003 = OD_CNT_ARR_1003;\
|
||||
(config).ENTRY_H1003 = OD_ENTRY_H1003;\
|
||||
(config).CNT_SDO_SRV = OD_CNT_SDO_SRV;\
|
||||
(config).ENTRY_H1200 = OD_ENTRY_H1200;\
|
||||
(config).CNT_SDO_CLI = OD_CNT_SDO_CLI;\
|
||||
(config).ENTRY_H1280 = OD_ENTRY_H1280;\
|
||||
(config).CNT_TIME = OD_CNT_TIME;\
|
||||
(config).ENTRY_H1012 = OD_ENTRY_H1012;\
|
||||
(config).CNT_SYNC = OD_CNT_SYNC;\
|
||||
(config).ENTRY_H1005 = OD_ENTRY_H1005;\
|
||||
(config).ENTRY_H1006 = OD_ENTRY_H1006;\
|
||||
(config).ENTRY_H1007 = OD_ENTRY_H1007;\
|
||||
(config).ENTRY_H1019 = OD_ENTRY_H1019;\
|
||||
(config).CNT_RPDO = OD_CNT_RPDO;\
|
||||
(config).ENTRY_H1400 = OD_ENTRY_H1400;\
|
||||
(config).ENTRY_H1600 = OD_ENTRY_H1600;\
|
||||
(config).CNT_TPDO = OD_CNT_TPDO;\
|
||||
(config).ENTRY_H1800 = OD_ENTRY_H1800;\
|
||||
(config).ENTRY_H1A00 = OD_ENTRY_H1A00;\
|
||||
(config).CNT_LEDS = 0;\
|
||||
(config).CNT_GFC = 0;\
|
||||
(config).ENTRY_H1300 = NULL;\
|
||||
(config).CNT_SRDO = 0;\
|
||||
(config).ENTRY_H1301 = NULL;\
|
||||
(config).ENTRY_H1381 = NULL;\
|
||||
(config).ENTRY_H13FE = NULL;\
|
||||
(config).ENTRY_H13FF = NULL;\
|
||||
(config).CNT_LSS_SLV = 0;\
|
||||
(config).CNT_LSS_MST = 0;\
|
||||
(config).CNT_GTWA = 0;\
|
||||
(config).CNT_TRACE = 0;\
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OD_H */
|
||||
2350
Middleware/CANopenNode/NodeSlave.xdd
Normal file
2350
Middleware/CANopenNode/NodeSlave.xdd
Normal file
File diff suppressed because it is too large
Load Diff
506
Middleware/CANopenNode/extra/CO_trace.c
Normal file
506
Middleware/CANopenNode/extra/CO_trace.c
Normal file
@@ -0,0 +1,506 @@
|
||||
/*
|
||||
* CANopen trace interface.
|
||||
*
|
||||
* @file CO_trace.c
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2016 - 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 "extra/CO_trace.h"
|
||||
|
||||
#if (CO_CONFIG_TRACE) & CO_CONFIG_TRACE_ENABLE
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#if !((CO_CONFIG_TRACE) & CO_CONFIG_TRACE_OWN_INTTYPES)
|
||||
#include <inttypes.h> /* for PRIu32("u" or "lu") and PRId32("d" or "ld") */
|
||||
#endif
|
||||
|
||||
/* Different functions for processing value for different data types. */
|
||||
static int32_t getValueI8 (void *OD_variable) { return (int32_t) *((int8_t*) OD_variable);}
|
||||
static int32_t getValueI16(void *OD_variable) { return (int32_t) *((int16_t*) OD_variable);}
|
||||
static int32_t getValueI32(void *OD_variable) { return *((int32_t*) OD_variable);}
|
||||
static int32_t getValueU8 (void *OD_variable) { return (int32_t) *((uint8_t*) OD_variable);}
|
||||
static int32_t getValueU16(void *OD_variable) { return (int32_t) *((uint16_t*) OD_variable);}
|
||||
static int32_t getValueU32(void *OD_variable) { return *((int32_t*) OD_variable);}
|
||||
|
||||
|
||||
/* Different functions for printing points for different data types. */
|
||||
static uint32_t printPointCsv(char *s, uint32_t size, uint32_t timeStamp, int32_t value) {
|
||||
return snprintf(s, size, "%" PRIu32 ";%" PRId32 "\n", timeStamp, value);
|
||||
}
|
||||
static uint32_t printPointCsvUnsigned(char *s, uint32_t size, uint32_t timeStamp, int32_t value) {
|
||||
return snprintf(s, size, "%" PRIu32 ";%" PRIu32 "\n", timeStamp, (uint32_t) value);
|
||||
}
|
||||
static uint32_t printPointBinary(char *s, uint32_t size, uint32_t timeStamp, int32_t value) {
|
||||
if(size < 8) return 0;
|
||||
uint32_t timeStampSw = CO_SWAP_32(timeStamp);
|
||||
int32_t valueSw = CO_SWAP_32(value);
|
||||
memcpy(s, &timeStampSw, sizeof(timeStampSw));
|
||||
memcpy(s+4, &valueSw, sizeof(valueSw));
|
||||
return 8;
|
||||
}
|
||||
static uint32_t printPointSvgStart(char *s, uint32_t size, uint32_t timeStamp, int32_t value) {
|
||||
return snprintf(s, size, "M%" PRIu32 ",%" PRId32, timeStamp, value);
|
||||
}
|
||||
static uint32_t printPointSvgStartUnsigned(char *s, uint32_t size, uint32_t timeStamp, int32_t value) {
|
||||
return snprintf(s, size, "M%" PRIu32 ",%" PRIu32, timeStamp, (uint32_t) value);
|
||||
}
|
||||
static uint32_t printPointSvg(char *s, uint32_t size, uint32_t timeStamp, int32_t value) {
|
||||
return snprintf(s, size, "H%" PRIu32 "V%" PRId32, timeStamp, value);
|
||||
}
|
||||
static uint32_t printPointSvgUnsigned(char *s, uint32_t size, uint32_t timeStamp, int32_t value) {
|
||||
return snprintf(s, size, "H%" PRIu32 "V%" PRIu32, timeStamp, (uint32_t) value);
|
||||
}
|
||||
|
||||
|
||||
/* Collection of function pointers for fast processing based on specific data type. */
|
||||
/* Rules for the array: There must be groups of six members (I8, I16, I32, U8, U16, U32)
|
||||
* in correct order and sequence, so findVariable() finds correct member. */
|
||||
static const CO_trace_dataType_t dataTypes[] = {
|
||||
{getValueI8, printPointCsv, printPointCsv, printPointCsv},
|
||||
{getValueI16, printPointCsv, printPointCsv, printPointCsv},
|
||||
{getValueI32, printPointCsv, printPointCsv, printPointCsv},
|
||||
{getValueU8, printPointCsvUnsigned, printPointCsvUnsigned, printPointCsvUnsigned},
|
||||
{getValueU16, printPointCsvUnsigned, printPointCsvUnsigned, printPointCsvUnsigned},
|
||||
{getValueU32, printPointCsvUnsigned, printPointCsvUnsigned, printPointCsvUnsigned},
|
||||
{getValueI8, printPointBinary, printPointBinary, printPointBinary},
|
||||
{getValueI16, printPointBinary, printPointBinary, printPointBinary},
|
||||
{getValueI32, printPointBinary, printPointBinary, printPointBinary},
|
||||
{getValueU8, printPointBinary, printPointBinary, printPointBinary},
|
||||
{getValueU16, printPointBinary, printPointBinary, printPointBinary},
|
||||
{getValueU32, printPointBinary, printPointBinary, printPointBinary},
|
||||
{getValueI8, printPointSvgStart, printPointSvg, printPointSvg},
|
||||
{getValueI16, printPointSvgStart, printPointSvg, printPointSvg},
|
||||
{getValueI32, printPointSvgStart, printPointSvg, printPointSvg},
|
||||
{getValueU8, printPointSvgStartUnsigned, printPointSvgUnsigned, printPointSvgUnsigned},
|
||||
{getValueU16, printPointSvgStartUnsigned, printPointSvgUnsigned, printPointSvgUnsigned},
|
||||
{getValueU32, printPointSvgStartUnsigned, printPointSvgUnsigned, printPointSvgUnsigned}
|
||||
};
|
||||
|
||||
|
||||
/* Find variable in Object Dictionary *****************************************/
|
||||
static void findVariable(CO_trace_t *trace) {
|
||||
bool_t err = false;
|
||||
uint16_t index;
|
||||
uint8_t subIndex;
|
||||
uint8_t dataLen;
|
||||
void *OdDataPtr = NULL;
|
||||
unsigned dtIndex = 0;
|
||||
|
||||
/* parse mapping */
|
||||
index = (uint16_t) ((*trace->map) >> 16);
|
||||
subIndex = (uint8_t) ((*trace->map) >> 8);
|
||||
dataLen = (uint8_t) (*trace->map);
|
||||
if((dataLen & 0x07) != 0) { /* data length must be byte aligned */
|
||||
err = true;
|
||||
}
|
||||
dataLen >>= 3; /* in bytes now */
|
||||
if(dataLen == 0) {
|
||||
dataLen = 4;
|
||||
}
|
||||
|
||||
/* find mapped variable, if map available */
|
||||
if(!err && (index != 0 || subIndex != 0)) {
|
||||
uint16_t entryNo = CO_OD_find(trace->SDO, index);
|
||||
|
||||
if(index >= 0x1000 && entryNo != 0xFFFF && subIndex <= trace->SDO->OD[entryNo].maxSubIndex) {
|
||||
OdDataPtr = CO_OD_getDataPointer(trace->SDO, entryNo, subIndex);
|
||||
}
|
||||
|
||||
if(OdDataPtr != NULL) {
|
||||
uint16_t len = CO_OD_getLength(trace->SDO, entryNo, subIndex);
|
||||
|
||||
if(len < dataLen) {
|
||||
dataLen = len;
|
||||
}
|
||||
}
|
||||
else {
|
||||
err = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get function pointers for correct data type */
|
||||
if(!err) {
|
||||
/* first sequence: data length */
|
||||
switch(dataLen) {
|
||||
case 1: dtIndex = 0; break;
|
||||
case 2: dtIndex = 1; break;
|
||||
case 4: dtIndex = 2; break;
|
||||
default: err = true; break;
|
||||
}
|
||||
/* second sequence: signed or unsigned */
|
||||
if(((*trace->format) & 1) == 1) {
|
||||
dtIndex += 3;
|
||||
}
|
||||
/* third sequence: Output type */
|
||||
dtIndex += ((*trace->format) >> 1) * 6;
|
||||
|
||||
if(dtIndex > (sizeof(dataTypes) / sizeof(CO_trace_dataType_t))) {
|
||||
err = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* set output variables */
|
||||
if(!err) {
|
||||
if(OdDataPtr != NULL) {
|
||||
trace->OD_variable = OdDataPtr;
|
||||
}
|
||||
else {
|
||||
trace->OD_variable = trace->value;
|
||||
}
|
||||
trace->dt = &dataTypes[dtIndex];
|
||||
}
|
||||
else {
|
||||
trace->OD_variable = NULL;
|
||||
trace->dt = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* OD function for accessing _OD_traceConfig_ (index 0x2300+) from SDO server.
|
||||
* For more information see file CO_SDOserver.h. */
|
||||
static CO_SDO_abortCode_t CO_ODF_traceConfig(CO_ODF_arg_t *ODF_arg) {
|
||||
CO_trace_t *trace;
|
||||
CO_SDO_abortCode_t ret = CO_SDO_AB_NONE;
|
||||
|
||||
trace = (CO_trace_t*) ODF_arg->object;
|
||||
|
||||
switch(ODF_arg->subIndex) {
|
||||
case 1: /* size */
|
||||
if(ODF_arg->reading) {
|
||||
CO_setUint32(ODF_arg->data, trace->bufferSize);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: /* axisNo (trace enabled if nonzero) */
|
||||
if(ODF_arg->reading) {
|
||||
uint8_t *value = (uint8_t*) ODF_arg->data;
|
||||
if(!trace->enabled) {
|
||||
*value = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
uint8_t *value = (uint8_t*) ODF_arg->data;
|
||||
|
||||
if(*value == 0) {
|
||||
trace->enabled = false;
|
||||
}
|
||||
else if(!trace->enabled) {
|
||||
if(trace->bufferSize == 0) {
|
||||
ret = CO_SDO_AB_OUT_OF_MEM;
|
||||
}
|
||||
else {
|
||||
/* set trace->OD_variable and trace->dt, based on 'map' and 'format' */
|
||||
findVariable(trace);
|
||||
|
||||
if(trace->OD_variable != NULL) {
|
||||
*trace->value = 0;
|
||||
*trace->minValue = 0;
|
||||
*trace->maxValue = 0;
|
||||
*trace->triggerTime = 0;
|
||||
trace->valuePrev = 0;
|
||||
trace->readPtr = 0;
|
||||
trace->writePtr = 0;
|
||||
trace->enabled = true;
|
||||
}
|
||||
else {
|
||||
ret = CO_SDO_AB_NO_MAP;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 5: /* map */
|
||||
case 6: /* format */
|
||||
if(!ODF_arg->reading) {
|
||||
if(trace->enabled) {
|
||||
ret = CO_SDO_AB_INVALID_VALUE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* MISRA C 2004 15.3 */
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* OD function for accessing _OD_trace_ (index 0x2400+) from SDO server.
|
||||
* For more information see file CO_SDOserver.h. */
|
||||
static CO_SDO_abortCode_t CO_ODF_trace(CO_ODF_arg_t *ODF_arg) {
|
||||
CO_trace_t *trace;
|
||||
CO_SDO_abortCode_t ret = CO_SDO_AB_NONE;
|
||||
|
||||
trace = (CO_trace_t*) ODF_arg->object;
|
||||
|
||||
switch(ODF_arg->subIndex) {
|
||||
case 1: /* size */
|
||||
if(ODF_arg->reading) {
|
||||
uint32_t size = trace->bufferSize;
|
||||
uint32_t wp = trace->writePtr;
|
||||
uint32_t rp = trace->readPtr;
|
||||
|
||||
if(wp >= rp) {
|
||||
CO_setUint32(ODF_arg->data, wp - rp);
|
||||
}
|
||||
else {
|
||||
CO_setUint32(ODF_arg->data, size - rp + wp);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(CO_getUint32(ODF_arg->data) == 0) {
|
||||
/* clear buffer, handle race conditions */
|
||||
while(trace->readPtr != 0 || trace->writePtr != 0) {
|
||||
trace->readPtr = 0;
|
||||
trace->writePtr = 0;
|
||||
*trace->triggerTime = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ret = CO_SDO_AB_INVALID_VALUE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 5: /* plot */
|
||||
if(ODF_arg->reading) {
|
||||
/* This plot will be transmitted as domain data type. String data
|
||||
* will be printed directly to SDO buffer. If there is more data
|
||||
* to print, than is the size of SDO buffer, then this function
|
||||
* will be called multiple times until internal trace buffer is
|
||||
* empty. Internal trace buffer is circular buffer. It is accessed
|
||||
* by this function and by higher priority thread. If this buffer
|
||||
* is full, there is a danger for race condition. First records
|
||||
* from trace buffer may be overwritten somewhere between. If this
|
||||
* is detected, then do{}while() loop tries printing again. */
|
||||
if(trace->bufferSize == 0 || ODF_arg->dataLength < 100) {
|
||||
ret = CO_SDO_AB_OUT_OF_MEM;
|
||||
}
|
||||
else if(trace->readPtr == trace->writePtr) {
|
||||
ret = CO_SDO_AB_NO_DATA;
|
||||
}
|
||||
else {
|
||||
uint32_t rp, t, v, len, freeLen;
|
||||
char *s;
|
||||
bool_t readPtrOverflowed; /* for handling race conditions */
|
||||
|
||||
/* repeat everything, if trace->readPtr was overflowed in CO_trace_process */
|
||||
do {
|
||||
readPtrOverflowed = false;
|
||||
s = (char*) ODF_arg->data;
|
||||
freeLen = ODF_arg->dataLength;
|
||||
|
||||
rp = trace->readPtr;
|
||||
|
||||
/* start plot, increment variables, verify overflow */
|
||||
if(ODF_arg->firstSegment) {
|
||||
t = trace->timeBuffer[rp];
|
||||
v = trace->valueBuffer[rp];
|
||||
rp ++;
|
||||
if(++trace->readPtr == trace->bufferSize) {
|
||||
trace->readPtr = 0;
|
||||
if(rp != trace->bufferSize) {
|
||||
readPtrOverflowed = true;
|
||||
continue;
|
||||
}
|
||||
rp = 0;
|
||||
}
|
||||
if(rp != trace->readPtr) {
|
||||
readPtrOverflowed = true;
|
||||
continue;
|
||||
}
|
||||
len = trace->dt->printPointStart(s, freeLen, t, v);
|
||||
s += len;
|
||||
freeLen -= len;
|
||||
}
|
||||
|
||||
/* print other points */
|
||||
if(rp != trace->writePtr) {
|
||||
for(;;) {
|
||||
t = trace->timeBuffer[rp];
|
||||
v = trace->valueBuffer[rp];
|
||||
rp ++;
|
||||
if(++trace->readPtr == trace->bufferSize) {
|
||||
trace->readPtr = 0;
|
||||
if(rp != trace->bufferSize && ODF_arg->firstSegment) {
|
||||
readPtrOverflowed = true;
|
||||
break;
|
||||
}
|
||||
rp = 0;
|
||||
}
|
||||
if(rp != trace->readPtr && ODF_arg->firstSegment) {
|
||||
readPtrOverflowed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* If internal buffer is empty, end transfer */
|
||||
if(rp == trace->writePtr) {
|
||||
/* If there is last time stamp, point will be printed at the end */
|
||||
if(t != trace->lastTimeStamp) {
|
||||
len = trace->dt->printPoint(s, freeLen, t, v);
|
||||
s += len;
|
||||
freeLen -= len;
|
||||
}
|
||||
ODF_arg->lastSegment = true;
|
||||
break;
|
||||
}
|
||||
len = trace->dt->printPoint(s, freeLen, t, v);
|
||||
s += len;
|
||||
freeLen -= len;
|
||||
|
||||
/* if output buffer is full, next data will be sent later */
|
||||
if(freeLen < 50) {
|
||||
ODF_arg->lastSegment = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* print last point */
|
||||
if(!readPtrOverflowed && ODF_arg->lastSegment) {
|
||||
v = trace->valuePrev;
|
||||
t = trace->lastTimeStamp;
|
||||
len = trace->dt->printPointEnd(s, freeLen, t, v);
|
||||
s += len;
|
||||
freeLen -= len;
|
||||
}
|
||||
} while(readPtrOverflowed);
|
||||
|
||||
ODF_arg->dataLength -= freeLen;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* MISRA C 2004 15.3 */
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void CO_trace_init(
|
||||
CO_trace_t *trace,
|
||||
CO_SDO_t *SDO,
|
||||
uint8_t enabled,
|
||||
uint32_t *timeBuffer,
|
||||
int32_t *valueBuffer,
|
||||
uint32_t bufferSize,
|
||||
uint32_t *map,
|
||||
uint8_t *format,
|
||||
uint8_t *trigger,
|
||||
int32_t *threshold,
|
||||
int32_t *value,
|
||||
int32_t *minValue,
|
||||
int32_t *maxValue,
|
||||
uint32_t *triggerTime,
|
||||
uint16_t idx_OD_traceConfig,
|
||||
uint16_t idx_OD_trace)
|
||||
{
|
||||
trace->SDO = SDO;
|
||||
trace->enabled = (enabled != 0) ? true : false;
|
||||
trace->timeBuffer = timeBuffer;
|
||||
trace->valueBuffer = valueBuffer;
|
||||
trace->bufferSize = bufferSize;
|
||||
trace->writePtr = 0;
|
||||
trace->readPtr = 0;
|
||||
trace->lastTimeStamp = 0;
|
||||
trace->map = map;
|
||||
trace->format = format;
|
||||
trace->trigger = trigger;
|
||||
trace->threshold = threshold;
|
||||
trace->value = value;
|
||||
trace->minValue = minValue;
|
||||
trace->maxValue = maxValue;
|
||||
trace->triggerTime = triggerTime;
|
||||
*trace->value = 0;
|
||||
*trace->minValue = 0;
|
||||
*trace->maxValue = 0;
|
||||
*trace->triggerTime = 0;
|
||||
trace->valuePrev = 0;
|
||||
|
||||
/* set trace->OD_variable and trace->dt, based on 'map' and 'format' */
|
||||
findVariable(trace);
|
||||
|
||||
if(timeBuffer == NULL || valueBuffer == NULL) {
|
||||
trace->bufferSize = 0;
|
||||
}
|
||||
|
||||
if( trace->bufferSize == 0 || trace->OD_variable == NULL) {
|
||||
trace->enabled = false;
|
||||
}
|
||||
|
||||
CO_OD_configure(SDO, idx_OD_traceConfig, CO_ODF_traceConfig, (void*)trace, 0, 0);
|
||||
CO_OD_configure(SDO, idx_OD_trace, CO_ODF_trace, (void*)trace, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
void CO_trace_process(CO_trace_t *trace, uint32_t timestamp) {
|
||||
if(trace->enabled) {
|
||||
|
||||
int32_t val = trace->dt->pGetValue(trace->OD_variable);
|
||||
|
||||
if(val != trace->valuePrev) {
|
||||
/* Verify, if value passed threshold */
|
||||
if((*trace->trigger & 1) != 0 && trace->valuePrev < *trace->threshold && val >= *trace->threshold) {
|
||||
*trace->triggerTime = timestamp;
|
||||
}
|
||||
if((*trace->trigger & 2) != 0 && trace->valuePrev < *trace->threshold && val >= *trace->threshold) {
|
||||
*trace->triggerTime = timestamp;
|
||||
}
|
||||
|
||||
/* Write value and verify min/max */
|
||||
if(trace->value != trace->OD_variable) {
|
||||
*trace->value = val;
|
||||
}
|
||||
trace->valuePrev = val;
|
||||
if(*trace->minValue > val) {
|
||||
*trace->minValue = val;
|
||||
}
|
||||
if(*trace->maxValue < val) {
|
||||
*trace->maxValue = val;
|
||||
}
|
||||
|
||||
/* write buffers and update pointers */
|
||||
trace->timeBuffer[trace->writePtr] = timestamp;
|
||||
trace->valueBuffer[trace->writePtr] = val;
|
||||
if(++trace->writePtr == trace->bufferSize) {
|
||||
trace->writePtr = 0;
|
||||
}
|
||||
if(trace->writePtr == trace->readPtr) {
|
||||
if(++trace->readPtr == trace->bufferSize) {
|
||||
trace->readPtr = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* if buffer is empty, make first record */
|
||||
if(trace->writePtr == trace->readPtr) {
|
||||
/* write buffers and update pointers */
|
||||
trace->timeBuffer[trace->writePtr] = timestamp;
|
||||
trace->valueBuffer[trace->writePtr] = val;
|
||||
if(++trace->writePtr == trace->bufferSize) {
|
||||
trace->writePtr = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
trace->lastTimeStamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* (CO_CONFIG_TRACE) & CO_CONFIG_TRACE_ENABLE */
|
||||
169
Middleware/CANopenNode/extra/CO_trace.h
Normal file
169
Middleware/CANopenNode/extra/CO_trace.h
Normal file
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* CANopen trace object for recording variables over time.
|
||||
*
|
||||
* @file CO_trace.h
|
||||
* @ingroup CO_trace
|
||||
* @author Janez Paternoster
|
||||
* @copyright 2016 - 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_TRACE_H
|
||||
#define CO_TRACE_H
|
||||
|
||||
#include "301/CO_driver.h"
|
||||
#include "301/CO_SDOserver.h"
|
||||
|
||||
/* default configuration, see CO_config.h */
|
||||
#ifndef CO_CONFIG_TRACE
|
||||
#define CO_CONFIG_TRACE (0)
|
||||
#endif
|
||||
|
||||
#if ((CO_CONFIG_TRACE) & CO_CONFIG_TRACE_ENABLE) || defined CO_DOXYGEN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_trace Trace
|
||||
* CANopen trace object for recording variables over time.
|
||||
*
|
||||
* @ingroup CO_CANopen_extra
|
||||
* @{
|
||||
* In embedded systems there is often a need to monitor some variables over time.
|
||||
* Results are then displayed on graph, similar as in oscilloscope.
|
||||
*
|
||||
* CANopen trace is a configurable object, accessible via CANopen Object
|
||||
* Dictionary, which records chosen variable over time. It generates a curve,
|
||||
* which can be read via SDO and can then be displayed in a graph.
|
||||
*
|
||||
* CO_trace_process() runs in 1 ms intervals and monitors one variable. If it
|
||||
* changes, it makes a record with timestamp into circular buffer. When trace is
|
||||
* accessed by CANopen SDO object, it reads latest points from the the circular
|
||||
* buffer, prints a SVG curve into string and sends it as a SDO response. If a
|
||||
* SDO request was received from the same device, then no traffic occupies CAN
|
||||
* network.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Start index of traceConfig and Trace objects in Object Dictionary.
|
||||
*/
|
||||
#ifndef OD_INDEX_TRACE_CONFIG
|
||||
#define OD_INDEX_TRACE_CONFIG 0x2301
|
||||
#define OD_INDEX_TRACE 0x2401
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* structure for reading variables and printing points for specific data type.
|
||||
*/
|
||||
typedef struct {
|
||||
/** Function pointer for getting the value from OD variable. **/
|
||||
int32_t (*pGetValue) (void *OD_variable);
|
||||
/** Function pointer for printing the start point to trace.plot */
|
||||
uint32_t (*printPointStart)(char *s, uint32_t size, uint32_t timeStamp, int32_t value);
|
||||
/** Function pointer for printing the point to trace.plot */
|
||||
uint32_t (*printPoint)(char *s, uint32_t size, uint32_t timeStamp, int32_t value);
|
||||
/** Function pointer for printing the end point to trace.plot */
|
||||
uint32_t (*printPointEnd)(char *s, uint32_t size, uint32_t timeStamp, int32_t value);
|
||||
} CO_trace_dataType_t;
|
||||
|
||||
|
||||
/**
|
||||
* Trace object.
|
||||
*/
|
||||
typedef struct {
|
||||
bool_t enabled; /**< True, if trace is enabled. */
|
||||
CO_SDO_t *SDO; /**< From CO_trace_init(). */
|
||||
uint32_t *timeBuffer; /**< From CO_trace_init(). */
|
||||
int32_t *valueBuffer; /**< From CO_trace_init(). */
|
||||
uint32_t bufferSize; /**< From CO_trace_init(). */
|
||||
volatile uint32_t writePtr; /**< Location in buffer, which will be next written. */
|
||||
volatile uint32_t readPtr; /**< Location in buffer, which will be next read. */
|
||||
uint32_t lastTimeStamp; /**< Last time stamp. If zero, then last point contains last timestamp. */
|
||||
void *OD_variable; /**< Pointer to variable, which is monitored */
|
||||
const CO_trace_dataType_t *dt; /**< Data type specific function pointers. **/
|
||||
int32_t valuePrev; /**< Previous value of value. */
|
||||
uint32_t *map; /**< From CO_trace_init(). */
|
||||
uint8_t *format; /**< From CO_trace_init(). */
|
||||
int32_t *value; /**< From CO_trace_init(). */
|
||||
int32_t *minValue; /**< From CO_trace_init(). */
|
||||
int32_t *maxValue; /**< From CO_trace_init(). */
|
||||
uint32_t *triggerTime; /**< From CO_trace_init(). */
|
||||
uint8_t *trigger; /**< From CO_trace_init(). */
|
||||
int32_t *threshold; /**< From CO_trace_init(). */
|
||||
} CO_trace_t;
|
||||
|
||||
|
||||
/**
|
||||
* Initialize trace object.
|
||||
*
|
||||
* Function must be called in the communication reset section.
|
||||
*
|
||||
* @param trace This object will be initialized.
|
||||
* @param SDO SDO server object.
|
||||
* @param enabled Is trace enabled.
|
||||
* @param timeBuffer Memory block for storing time records.
|
||||
* @param valueBuffer Memory block for storing value records.
|
||||
* @param bufferSize Size of the above buffers.
|
||||
* @param map Map to variable in Object Dictionary, which will be monitored. Same structure as in PDO.
|
||||
* @param format Format of the plot. If first bit is 1, above variable is unsigned. For more info see Object Dictionary.
|
||||
* @param trigger If different than zero, trigger time is recorded, when variable goes through threshold.
|
||||
* @param threshold Used with trigger.
|
||||
* @param value Pointer to variable, which will show last value of the variable.
|
||||
* @param minValue Pointer to variable, which will show minimum value of the variable.
|
||||
* @param maxValue Pointer to variable, which will show maximum value of the variable.
|
||||
* @param triggerTime Pointer to variable, which will show last trigger time of the variable.
|
||||
* @param idx_OD_traceConfig Index in Object Dictionary.
|
||||
* @param idx_OD_trace Index in Object Dictionary.
|
||||
*/
|
||||
void CO_trace_init(
|
||||
CO_trace_t *trace,
|
||||
CO_SDO_t *SDO,
|
||||
uint8_t enabled,
|
||||
uint32_t *timeBuffer,
|
||||
int32_t *valueBuffer,
|
||||
uint32_t bufferSize,
|
||||
uint32_t *map,
|
||||
uint8_t *format,
|
||||
uint8_t *trigger,
|
||||
int32_t *threshold,
|
||||
int32_t *value,
|
||||
int32_t *minValue,
|
||||
int32_t *maxValue,
|
||||
uint32_t *triggerTime,
|
||||
uint16_t idx_OD_traceConfig,
|
||||
uint16_t idx_OD_trace);
|
||||
|
||||
|
||||
/**
|
||||
* Process trace object.
|
||||
*
|
||||
* Function must be called cyclically in 1ms intervals.
|
||||
*
|
||||
* @param trace This object.
|
||||
* @param timestamp Timestamp (usually in millisecond resolution).
|
||||
*/
|
||||
void CO_trace_process(CO_trace_t *trace, uint32_t timestamp);
|
||||
|
||||
/** @} */ /* CO_trace */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_TRACE) & CO_CONFIG_TRACE_ENABLE */
|
||||
|
||||
#endif /* CO_TRACE_H */
|
||||
113
Middleware/CANopenNode/storage/CO_eeprom.h
Normal file
113
Middleware/CANopenNode/storage/CO_eeprom.h
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Eeprom interface for use with CO_storageEeprom
|
||||
*
|
||||
* @file CO_eeprom.h
|
||||
* @ingroup CO_storage_eeprom
|
||||
* @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_EEPROM_H
|
||||
#define CO_EEPROM_H
|
||||
|
||||
#include "301/CO_driver.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @addtogroup CO_storage_eeprom
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Initialize eeprom device, target system specific function.
|
||||
*
|
||||
* @param storageModule Pointer to storage module.
|
||||
*
|
||||
* @return True on success
|
||||
*/
|
||||
bool_t CO_eeprom_init(void* storageModule);
|
||||
|
||||
/**
|
||||
* Get free address inside eeprom, target system specific function.
|
||||
*
|
||||
* Function is called several times for each storage block in the initialization phase after CO_eeprom_init().
|
||||
*
|
||||
* @param storageModule Pointer to storage module.
|
||||
* @param isAuto True, if variable is auto stored or false if protected
|
||||
* @param len Length of data, which will be stored to that location
|
||||
* @param [out] overflow set to true, if not enough eeprom memory
|
||||
*
|
||||
* @return Asigned eeprom address
|
||||
*/
|
||||
size_t CO_eeprom_getAddr(void* storageModule, bool_t isAuto, size_t len, bool_t* overflow);
|
||||
|
||||
/**
|
||||
* Read block of data from the eeprom, target system specific function.
|
||||
*
|
||||
* @param storageModule Pointer to storage module.
|
||||
* @param data Pointer to data buffer, where data will be stored.
|
||||
* @param eepromAddr Address in eeprom, from where data will be read.
|
||||
* @param len Length of the data block to be read.
|
||||
*/
|
||||
void CO_eeprom_readBlock(void* storageModule, uint8_t* data, size_t eepromAddr, size_t len);
|
||||
|
||||
/**
|
||||
* Write block of data to the eeprom, target system specific function.
|
||||
*
|
||||
* It is blocking function, so it waits, until all data is written.
|
||||
*
|
||||
* @param storageModule Pointer to storage module.
|
||||
* @param data Pointer to data buffer which will be written.
|
||||
* @param eepromAddr Address in eeprom, where data will be written. If data is stored across multiple pages, address
|
||||
* must be aligned with page.
|
||||
* @param len Length of the data block.
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool_t CO_eeprom_writeBlock(void* storageModule, uint8_t* data, size_t eepromAddr, size_t len);
|
||||
|
||||
/**
|
||||
* Get CRC checksum of the block of data stored in the eeprom, target system specific function.
|
||||
*
|
||||
* @param storageModule Pointer to storage module.
|
||||
* @param eepromAddr Address of data in eeprom.
|
||||
* @param len Length of the data.
|
||||
*
|
||||
* @return CRC checksum
|
||||
*/
|
||||
uint16_t CO_eeprom_getCrcBlock(void* storageModule, size_t eepromAddr, size_t len);
|
||||
|
||||
/**
|
||||
* Update one byte of data in the eeprom, target system specific function.
|
||||
*
|
||||
* Function is used by automatic storage. It updates byte in eeprom only if differs from data.
|
||||
*
|
||||
* @param storageModule Pointer to storage module.
|
||||
* @param data Data byte to be written
|
||||
* @param eepromAddr Address in eeprom, from where data will be updated.
|
||||
*
|
||||
* @return true if write was successful or false, if still waiting previous data to finish writing.
|
||||
*/
|
||||
bool_t CO_eeprom_updateByte(void* storageModule, uint8_t data, size_t eepromAddr);
|
||||
|
||||
/** @} */ /* CO_storage_eeprom */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CO_EEPROM_H */
|
||||
165
Middleware/CANopenNode/storage/CO_storage.c
Normal file
165
Middleware/CANopenNode/storage/CO_storage.c
Normal file
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* CANopen data storage base object
|
||||
*
|
||||
* @file CO_storage.c
|
||||
* @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 "storage/CO_storage.h"
|
||||
|
||||
#if ((CO_CONFIG_STORAGE)&CO_CONFIG_STORAGE_ENABLE) != 0
|
||||
|
||||
/*
|
||||
* Custom function for writing OD object "Store parameters"
|
||||
*
|
||||
* For more information see file CO_ODinterface.h, OD_IO_t.
|
||||
*/
|
||||
static ODR_t
|
||||
OD_write_1010(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
|
||||
/* verify arguments */
|
||||
if ((stream == NULL) || (stream->subIndex == 0U) || (buf == NULL) || (count != 4U) || (countWritten == NULL)) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
CO_storage_t* storage = stream->object;
|
||||
|
||||
if ((stream->subIndex == 0U) || (storage->store == NULL) || !storage->enabled) {
|
||||
return ODR_READONLY;
|
||||
}
|
||||
|
||||
uint32_t val = CO_getUint32(buf);
|
||||
if (val != 0x65766173U) {
|
||||
return ODR_DATA_TRANSF;
|
||||
}
|
||||
|
||||
/* loop through entries and store relevant */
|
||||
uint8_t found = 0;
|
||||
ODR_t returnCode = ODR_OK;
|
||||
|
||||
for (uint8_t i = 0; i < storage->entriesCount; i++) {
|
||||
CO_storage_entry_t* entry = &storage->entries[i];
|
||||
|
||||
if ((stream->subIndex == 1U) || (entry->subIndexOD == stream->subIndex)) {
|
||||
if (found == 0U) {
|
||||
found = 1;
|
||||
}
|
||||
if ((entry->attr & (uint8_t)CO_storage_cmd) != 0U) {
|
||||
ODR_t code = storage->store(entry, storage->CANmodule);
|
||||
if (code != ODR_OK) {
|
||||
returnCode = code;
|
||||
}
|
||||
found = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found != 2U) {
|
||||
returnCode = (found == 0U) ? ODR_SUB_NOT_EXIST : ODR_READONLY;
|
||||
}
|
||||
|
||||
if (returnCode == ODR_OK) {
|
||||
*countWritten = sizeof(uint32_t);
|
||||
}
|
||||
return returnCode;
|
||||
}
|
||||
|
||||
/*
|
||||
* Custom function for writing OD object "Restore default parameters"
|
||||
*
|
||||
* For more information see file CO_ODinterface.h, OD_IO_t.
|
||||
*/
|
||||
static ODR_t
|
||||
OD_write_1011(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
|
||||
/* verify arguments */
|
||||
if ((stream == NULL) || (stream->subIndex == 0U) || (buf == NULL) || (count != 4U) || (countWritten == NULL)) {
|
||||
return ODR_DEV_INCOMPAT;
|
||||
}
|
||||
|
||||
CO_storage_t* storage = stream->object;
|
||||
|
||||
if ((stream->subIndex == 0U) || (storage->restore == NULL) || !storage->enabled) {
|
||||
return ODR_READONLY;
|
||||
}
|
||||
|
||||
uint32_t val = CO_getUint32(buf);
|
||||
if (val != 0x64616F6CU) {
|
||||
return ODR_DATA_TRANSF;
|
||||
}
|
||||
|
||||
/* loop through entries and store relevant */
|
||||
uint8_t found = 0;
|
||||
ODR_t returnCode = ODR_OK;
|
||||
|
||||
for (uint8_t i = 0; i < storage->entriesCount; i++) {
|
||||
CO_storage_entry_t* entry = &storage->entries[i];
|
||||
|
||||
if ((stream->subIndex == 1U) || (entry->subIndexOD == stream->subIndex)) {
|
||||
if (found == 0U) {
|
||||
found = 1;
|
||||
}
|
||||
if ((entry->attr & (uint8_t)CO_storage_restore) != 0U) {
|
||||
ODR_t code = storage->restore(entry, storage->CANmodule);
|
||||
if (code != ODR_OK) {
|
||||
returnCode = code;
|
||||
}
|
||||
found = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found != 2U) {
|
||||
returnCode = (found == 0U) ? ODR_SUB_NOT_EXIST : ODR_READONLY;
|
||||
}
|
||||
|
||||
if (returnCode == ODR_OK) {
|
||||
*countWritten = sizeof(uint32_t);
|
||||
}
|
||||
return returnCode;
|
||||
}
|
||||
|
||||
CO_ReturnError_t
|
||||
CO_storage_init(CO_storage_t* storage, CO_CANmodule_t* CANmodule, OD_entry_t* OD_1010_StoreParameters,
|
||||
OD_entry_t* OD_1011_RestoreDefaultParameters,
|
||||
ODR_t (*store)(CO_storage_entry_t* entry, CO_CANmodule_t* CANmodule),
|
||||
ODR_t (*restore)(CO_storage_entry_t* entry, CO_CANmodule_t* CANmodule), CO_storage_entry_t* entries,
|
||||
uint8_t entriesCount) {
|
||||
/* verify arguments */
|
||||
if ((storage == NULL) || (CANmodule == NULL) || (OD_1010_StoreParameters == NULL)
|
||||
|| (OD_1011_RestoreDefaultParameters == NULL) || (store == NULL) || (restore == NULL) || (entries == NULL)) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* Configure object variables */
|
||||
storage->CANmodule = CANmodule;
|
||||
storage->store = store;
|
||||
storage->restore = restore;
|
||||
storage->entries = entries;
|
||||
storage->entriesCount = entriesCount;
|
||||
|
||||
/* configure extensions */
|
||||
storage->OD_1010_extension.object = storage;
|
||||
storage->OD_1010_extension.read = OD_readOriginal;
|
||||
storage->OD_1010_extension.write = OD_write_1010;
|
||||
(void)OD_extension_init(OD_1010_StoreParameters, &storage->OD_1010_extension);
|
||||
|
||||
storage->OD_1011_extension.object = storage;
|
||||
storage->OD_1011_extension.read = OD_readOriginal;
|
||||
storage->OD_1011_extension.write = OD_write_1011;
|
||||
(void)OD_extension_init(OD_1011_RestoreDefaultParameters, &storage->OD_1011_extension);
|
||||
|
||||
return CO_ERROR_NO;
|
||||
}
|
||||
|
||||
#endif /* (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE */
|
||||
142
Middleware/CANopenNode/storage/CO_storage.h
Normal file
142
Middleware/CANopenNode/storage/CO_storage.h
Normal file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* CANopen data storage base object
|
||||
*
|
||||
* @file CO_storage.h
|
||||
* @ingroup CO_storage
|
||||
* @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_STORAGE_H
|
||||
#define CO_STORAGE_H
|
||||
|
||||
#include "301/CO_driver.h"
|
||||
#include "301/CO_ODinterface.h"
|
||||
|
||||
/* default configuration, see CO_config.h */
|
||||
#ifndef CO_CONFIG_STORAGE
|
||||
#define CO_CONFIG_STORAGE (CO_CONFIG_STORAGE_ENABLE)
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_STORAGE)&CO_CONFIG_STORAGE_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_storage Data storage base
|
||||
* Base module for Data storage.
|
||||
*
|
||||
* @ingroup CO_CANopen_storage
|
||||
* @{
|
||||
*
|
||||
* CANopen provides OD objects 0x1010 and 0x1011 for control of storing and restoring data. Data source is usually a
|
||||
* group of variables inside object dictionary, but it is not limited to OD.
|
||||
*
|
||||
* When object dictionary is generated (OD.h and OD.c files), OD variables are grouped into structures according to
|
||||
* 'Storage group' parameter.
|
||||
*
|
||||
* Autonomous data storing must be implemented target specific, if in use.
|
||||
*
|
||||
* ### OD object 0x1010 - Store parameters:
|
||||
* - Sub index 0: Highest sub-index supported
|
||||
* - Sub index 1: Save all parameters, UNSIGNED32
|
||||
* - Sub index 2: Save communication parameters, UNSIGNED32
|
||||
* - Sub index 3: Save application parameters, UNSIGNED32
|
||||
* - Sub index 4 - 127: Manufacturer specific, UNSIGNED32
|
||||
*
|
||||
* Sub-indexes 1 and above:
|
||||
* - Reading provides information about its storage functionality:
|
||||
* - bit 0: If set, CANopen device saves parameters on command
|
||||
* - bit 1: If set, CANopen device saves parameters autonomously
|
||||
* - Writing value 0x65766173 ('s','a','v','e' from LSB to MSB) stores corresponding data.
|
||||
*
|
||||
* ### OD object 0x1011 - Restore default parameters
|
||||
* - Sub index 0: Highest sub-index supported
|
||||
* - Sub index 1: Restore all default parameters, UNSIGNED32
|
||||
* - Sub index 2: Restore communication default parameters, UNSIGNED32
|
||||
* - Sub index 3: Restore application default parameters, UNSIGNED32
|
||||
* - Sub index 4 - 127: Manufacturer specific, UNSIGNED32
|
||||
*
|
||||
* Sub-indexes 1 and above:
|
||||
* - Reading provides information about its restoring capability:
|
||||
* - bit 0: If set, CANopen device restores parameters
|
||||
* - Writing value 0x64616F6C ('l','o','a','d' from LSB to MSB) restores corresponding data.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Attributes (bit masks) for Data storage object.
|
||||
*/
|
||||
typedef enum {
|
||||
CO_storage_cmd = 0x01, /**< CANopen device saves parameters on OD 1010 command */
|
||||
CO_storage_auto = 0x02, /**< CANopen device saves parameters autonomously */
|
||||
CO_storage_restore = 0x04 /**< CANopen device restores parameters on OD 1011 command */
|
||||
} CO_storage_attributes_t;
|
||||
|
||||
/**
|
||||
* Data storage object.
|
||||
*
|
||||
* Object is used with CANopen OD objects at index 1010 and 1011.
|
||||
*/
|
||||
typedef struct {
|
||||
OD_extension_t OD_1010_extension; /**< Extension for OD object */
|
||||
OD_extension_t OD_1011_extension; /**< Extension for OD object */
|
||||
CO_CANmodule_t* CANmodule; /**< From CO_storage_init() */
|
||||
ODR_t (*store)(CO_storage_entry_t* entry, CO_CANmodule_t* CANmodule); /**< From CO_storage_init() */
|
||||
ODR_t (*restore)(CO_storage_entry_t* entry, CO_CANmodule_t* CANmodule); /**< From CO_storage_init() */
|
||||
CO_storage_entry_t* entries; /**< From CO_storage_init() */
|
||||
uint8_t entriesCount; /**< From CO_storage_init() */
|
||||
bool_t enabled; /**< true, if storage is enabled. Setting of this variable is implementation specific. */
|
||||
} CO_storage_t;
|
||||
|
||||
/**
|
||||
* Initialize data storage object
|
||||
*
|
||||
* This function should be called by application after the program startup, before @ref CO_CANopenInit(). This function
|
||||
* initializes storage object and OD extensions on objects 1010 and 1011. Function does not load stored data on startup,
|
||||
* because loading data is target specific.
|
||||
*
|
||||
* @param storage This object will be initialized. It must be defined by application and must exist permanently.
|
||||
* @param CANmodule CAN device, for optional usage.
|
||||
* @param OD_1010_StoreParameters OD entry for 0x1010 -"Store parameters". Entry is optional, may be NULL.
|
||||
* @param OD_1011_RestoreDefaultParameters OD entry for 0x1011 -"Restore default parameters". Entry is optional, may be
|
||||
* NULL.
|
||||
* @param store Pointer to externally defined function, which will store data specified by @ref CO_storage_entry_t.
|
||||
* Function will be called when OD variable 0x1010 will be written. Argument to function is entry, where
|
||||
* 'entry->subIndexOD' equals accessed subIndex. Function returns value from
|
||||
* @ref ODR_t : "ODR_OK" in case of success, "ODR_HW" in case of hardware error.
|
||||
* @param restore Same as 'store', but for restoring default data.
|
||||
* @param entries Pointer to array of storage entries. Array must be defined and initialized by application and must
|
||||
* exist permanently. Structure @ref CO_storage_entry_t is target specific and must be defined by CO_driver_target.h.
|
||||
* See CO_driver.h for required parameters.
|
||||
* @param entriesCount Count of storage entries
|
||||
*
|
||||
* @return CO_ERROR_NO or CO_ERROR_ILLEGAL_ARGUMENT.
|
||||
*/
|
||||
CO_ReturnError_t CO_storage_init(CO_storage_t* storage, CO_CANmodule_t* CANmodule, OD_entry_t* OD_1010_StoreParameters,
|
||||
OD_entry_t* OD_1011_RestoreDefaultParameters,
|
||||
ODR_t (*store)(CO_storage_entry_t* entry, CO_CANmodule_t* CANmodule),
|
||||
ODR_t (*restore)(CO_storage_entry_t* entry, CO_CANmodule_t* CANmodule),
|
||||
CO_storage_entry_t* entries, uint8_t entriesCount);
|
||||
|
||||
/** @} */ /* CO_storage */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE */
|
||||
|
||||
#endif /* CO_STORAGE_H */
|
||||
222
Middleware/CANopenNode/storage/CO_storageEeprom.c
Normal file
222
Middleware/CANopenNode/storage/CO_storageEeprom.c
Normal file
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* CANopen data storage object for storing data into block device (eeprom)
|
||||
*
|
||||
* @file CO_storageEeprom.c
|
||||
* @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 "storage/CO_storageEeprom.h"
|
||||
#include "storage/CO_eeprom.h"
|
||||
#include "301/crc16-ccitt.h"
|
||||
|
||||
#if ((CO_CONFIG_STORAGE)&CO_CONFIG_STORAGE_ENABLE) != 0
|
||||
|
||||
/*
|
||||
* Function for writing data on "Store parameters" command - OD object 1010
|
||||
*
|
||||
* For more information see file CO_storage.h, CO_storage_entry_t.
|
||||
*/
|
||||
static ODR_t
|
||||
storeEeprom(CO_storage_entry_t* entry, CO_CANmodule_t* CANmodule) {
|
||||
(void)CANmodule;
|
||||
bool_t writeOk;
|
||||
|
||||
/* save data to the eeprom */
|
||||
writeOk = CO_eeprom_writeBlock(entry->storageModule, entry->addr, entry->eepromAddr, entry->len);
|
||||
entry->crc = crc16_ccitt(entry->addr, entry->len, 0);
|
||||
|
||||
/* Verify, if data in eeprom are equal */
|
||||
uint16_t crc_read = CO_eeprom_getCrcBlock(entry->storageModule, entry->eepromAddr, entry->len);
|
||||
if ((entry->crc != crc_read) || !writeOk) {
|
||||
return ODR_HW;
|
||||
}
|
||||
|
||||
/* Write signature (see CO_storageEeprom_init() for info) */
|
||||
uint16_t signatureOfEntry = (uint16_t)entry->len;
|
||||
uint32_t signature = (((uint32_t)entry->crc) << 16) | signatureOfEntry;
|
||||
writeOk = CO_eeprom_writeBlock(entry->storageModule, (uint8_t*)&signature, entry->eepromAddrSignature,
|
||||
sizeof(signature));
|
||||
|
||||
/* verify signature and write */
|
||||
uint32_t signatureRead;
|
||||
CO_eeprom_readBlock(entry->storageModule, (uint8_t*)&signatureRead, entry->eepromAddrSignature,
|
||||
sizeof(signatureRead));
|
||||
if ((signature != signatureRead) || !writeOk) {
|
||||
return ODR_HW;
|
||||
}
|
||||
|
||||
return ODR_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Function for restoring data on "Restore default parameters" command - OD 1011
|
||||
*
|
||||
* For more information see file CO_storage.h, CO_storage_entry_t.
|
||||
*/
|
||||
static ODR_t
|
||||
restoreEeprom(CO_storage_entry_t* entry, CO_CANmodule_t* CANmodule) {
|
||||
(void)CANmodule;
|
||||
bool_t writeOk;
|
||||
|
||||
/* Write empty signature */
|
||||
uint32_t signature = 0xFFFFFFFFU;
|
||||
writeOk = CO_eeprom_writeBlock(entry->storageModule, (uint8_t*)&signature, entry->eepromAddrSignature,
|
||||
sizeof(signature));
|
||||
|
||||
/* verify signature and protection */
|
||||
uint32_t signatureRead;
|
||||
CO_eeprom_readBlock(entry->storageModule, (uint8_t*)&signatureRead, entry->eepromAddrSignature,
|
||||
sizeof(signatureRead));
|
||||
if ((signature != signatureRead) || !writeOk) {
|
||||
return ODR_HW;
|
||||
}
|
||||
|
||||
return ODR_OK;
|
||||
}
|
||||
|
||||
CO_ReturnError_t
|
||||
CO_storageEeprom_init(CO_storage_t* storage, CO_CANmodule_t* CANmodule, void* storageModule,
|
||||
OD_entry_t* OD_1010_StoreParameters, OD_entry_t* OD_1011_RestoreDefaultParam,
|
||||
CO_storage_entry_t* entries, uint8_t entriesCount, uint32_t* storageInitError) {
|
||||
CO_ReturnError_t ret;
|
||||
bool_t eepromOvf = false;
|
||||
|
||||
/* verify arguments */
|
||||
if ((storage == NULL) || (entries == NULL) || (entriesCount == 0U)
|
||||
|| (entriesCount > CO_CONFIG_STORAGE_MAX_ENTRIES_COUNT) || (storageInitError == NULL)) {
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
storage->enabled = false;
|
||||
|
||||
/* Initialize storage hardware */
|
||||
if (!CO_eeprom_init(storageModule)) {
|
||||
*storageInitError = 0xFFFFFFFFU;
|
||||
return CO_ERROR_DATA_CORRUPT;
|
||||
}
|
||||
|
||||
/* initialize storage and OD extensions */
|
||||
ret = CO_storage_init(storage, CANmodule, OD_1010_StoreParameters, OD_1011_RestoreDefaultParam, storeEeprom,
|
||||
restoreEeprom, entries, entriesCount);
|
||||
if (ret != CO_ERROR_NO) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Read entry signatures from the eeprom */
|
||||
uint32_t signatures[CO_CONFIG_STORAGE_MAX_ENTRIES_COUNT];
|
||||
size_t signaturesAddress = CO_eeprom_getAddr(storageModule, false, sizeof(signatures), &eepromOvf);
|
||||
CO_eeprom_readBlock(storageModule, (uint8_t*)signatures, signaturesAddress, sizeof(signatures));
|
||||
|
||||
/* initialize entries */
|
||||
*storageInitError = 0;
|
||||
for (uint8_t i = 0; i < entriesCount; i++) {
|
||||
CO_storage_entry_t* entry = &entries[i];
|
||||
bool_t isAuto = (entry->attr & (uint8_t)CO_storage_auto) != 0U;
|
||||
|
||||
/* verify arguments */
|
||||
if ((entry->addr == NULL) || (entry->len == 0U) || (entry->subIndexOD < 2U)) {
|
||||
*storageInitError = i;
|
||||
return CO_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
|
||||
/* calculate addresses inside eeprom */
|
||||
entry->eepromAddrSignature = signaturesAddress + (sizeof(uint32_t) * i);
|
||||
entry->eepromAddr = CO_eeprom_getAddr(storageModule, isAuto, entry->len, &eepromOvf);
|
||||
entry->offset = 0;
|
||||
|
||||
/* verify if eeprom is too small */
|
||||
if (eepromOvf) {
|
||||
*storageInitError = i;
|
||||
return CO_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
/* 32bit signature (which was stored in eeprom) is combined from
|
||||
* 16bit signature of the entry and 16bit CRC checksum of the data
|
||||
* block. 16bit signature of the entry is entry->len. */
|
||||
uint32_t signature = signatures[i];
|
||||
uint16_t signatureInEeprom = (uint16_t)signature;
|
||||
entry->crc = (uint16_t)(signature >> 16);
|
||||
uint16_t signatureOfEntry = (uint16_t)entry->len;
|
||||
|
||||
/* Verify two signatures */
|
||||
bool_t dataCorrupt = false;
|
||||
if (signatureInEeprom != signatureOfEntry) {
|
||||
dataCorrupt = true;
|
||||
} else {
|
||||
/* Read data into storage location */
|
||||
CO_eeprom_readBlock(entry->storageModule, entry->addr, entry->eepromAddr, entry->len);
|
||||
|
||||
/* Verify CRC, except for auto storage variables */
|
||||
if (!isAuto) {
|
||||
uint16_t crc = crc16_ccitt(entry->addr, entry->len, 0);
|
||||
if (crc != entry->crc) {
|
||||
dataCorrupt = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* additional info in case of error */
|
||||
if (dataCorrupt) {
|
||||
uint32_t errorBit = entry->subIndexOD;
|
||||
if (errorBit > 31U) {
|
||||
errorBit = 31;
|
||||
}
|
||||
*storageInitError |= ((uint32_t)1) << errorBit;
|
||||
ret = CO_ERROR_DATA_CORRUPT;
|
||||
}
|
||||
} /* for (entries) */
|
||||
|
||||
storage->enabled = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
CO_storageEeprom_auto_process(CO_storage_t* storage, bool_t saveAll) {
|
||||
/* verify arguments */
|
||||
if ((storage == NULL) || !storage->enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* loop through entries */
|
||||
for (uint8_t n = 0; n < storage->entriesCount; n++) {
|
||||
CO_storage_entry_t* entry = &storage->entries[n];
|
||||
|
||||
if ((entry->attr & (uint8_t)CO_storage_auto) == 0U) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (saveAll) {
|
||||
/* update all bytes */
|
||||
for (size_t i = 0; i < entry->len;) {
|
||||
uint8_t dataByteToUpdate = ((uint8_t*)(entry->addr))[i];
|
||||
size_t eepromAddr = entry->eepromAddr + i;
|
||||
if (CO_eeprom_updateByte(entry->storageModule, dataByteToUpdate, eepromAddr)) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* update one data byte and if successful increment to next */
|
||||
uint8_t dataByteToUpdate = ((uint8_t*)(entry->addr))[entry->offset];
|
||||
size_t eepromAddr = entry->eepromAddr + entry->offset;
|
||||
if (CO_eeprom_updateByte(entry->storageModule, dataByteToUpdate, eepromAddr)) {
|
||||
if (++entry->offset >= entry->len) {
|
||||
entry->offset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE */
|
||||
109
Middleware/CANopenNode/storage/CO_storageEeprom.h
Normal file
109
Middleware/CANopenNode/storage/CO_storageEeprom.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* CANopen data storage object for storing data into block device (eeprom)
|
||||
*
|
||||
* @file CO_storageEeprom.h
|
||||
* @ingroup CO_storage_eeprom
|
||||
* @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_STORAGE_EEPROM_H
|
||||
#define CO_STORAGE_EEPROM_H
|
||||
|
||||
#include "storage/CO_storage.h"
|
||||
|
||||
#ifndef CO_CONFIG_STORAGE_MAX_ENTRIES_COUNT
|
||||
#define CO_CONFIG_STORAGE_MAX_ENTRIES_COUNT 5U
|
||||
#endif
|
||||
|
||||
#if (((CO_CONFIG_STORAGE)&CO_CONFIG_STORAGE_ENABLE) != 0) || defined CO_DOXYGEN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup CO_storage_eeprom Data storage in eeprom
|
||||
* Eeprom specific data storage functions.
|
||||
*
|
||||
* @ingroup CO_CANopen_storage
|
||||
* @{
|
||||
* This is an interface into generic CANopenNode @ref CO_storage for usage with eeprom chip like 25LC256. Functions @ref
|
||||
* CO_storageEeprom_init() and @ref CO_storageEeprom_auto_process are target system independent. Functions specified by
|
||||
* @ref CO_eeprom.h file, must be defined by target system. For example implementation see CANopenPIC/PIC32.
|
||||
*
|
||||
* Storage principle:
|
||||
* This function first reads 'signatures' for all entries from the known address from the eeprom. If signature for each
|
||||
* entry is correct, then data is read from correct address from the eeprom into storage location. If signature is
|
||||
* wrong, then data for that entry is indicated as corrupt and CANopen emergency message is sent.
|
||||
*
|
||||
* Signature also includes 16-bit CRC checksum of the data stored in eeprom. If it differs from CRC checksum calculated
|
||||
* from the data actually loaded (on program startup), then entry is indicated as corrupt and CANopen emergency message
|
||||
* is sent.
|
||||
*
|
||||
* Signature is written to eeprom, when data block is stored via CANopen SDO write command to object 0x1010. Signature
|
||||
* is erased, with CANopen SDO write command to object 0x1011. If signature is not valid or is erased for any entry,
|
||||
* emergency message is sent. If eeprom is new, then all signatures are wrong, so it is best to store all parameters by
|
||||
* writing to 0x1010, sub 1.
|
||||
*
|
||||
* If entry attribute has CO_storage_auto set, then data block is stored autonomously, byte by byte, on change, during
|
||||
* program run. Those data blocks are stored into write unprotected location. For auto storage to work, its signature in
|
||||
* eeprom must be correct. CRC checksum for the data is not used.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Initialize data storage object (block device (eeprom) specific)
|
||||
*
|
||||
* This function should be called by application after the program startup, before @ref CO_CANopenInit(). This function
|
||||
* initializes storage object, OD extensions on objects 1010 and 1011, reads data from file, verifies them and writes
|
||||
* data to addresses specified inside entries. This function internally calls @ref CO_storage_init().
|
||||
*
|
||||
* @param storage This object will be initialized. It must be defined by application and must exist permanently.
|
||||
* @param CANmodule CAN device, for optional usage.
|
||||
* @param storageModule Pointer to storage module passed to CO_eeprom functions.
|
||||
* @param OD_1010_StoreParameters OD entry for 0x1010 -"Store parameters". Entry is optional, may be NULL.
|
||||
* @param OD_1011_RestoreDefaultParam OD entry for 0x1011 -"Restore default parameters". Entry is optional, may be NULL.
|
||||
* @param entries Pointer to array of storage entries, see @ref CO_storage_init.
|
||||
* @param entriesCount Count of storage entries, must not be larger than CO_CONFIG_STORAGE_MAX_ENTRIES_COUNT.
|
||||
* @param [out] storageInitError If function returns CO_ERROR_DATA_CORRUPT, then this variable contains a bit mask from
|
||||
* subIndexOD values, where data was not properly initialized. If other error, then this variable contains index or
|
||||
* erroneous entry. If there is hardware error like missing eeprom, then storageInitError is 0xFFFFFFFF and function
|
||||
* returns CO_ERROR_DATA_CORRUPT.
|
||||
*
|
||||
* @return CO_ERROR_NO, CO_ERROR_DATA_CORRUPT if data can not be initialized, CO_ERROR_ILLEGAL_ARGUMENT or
|
||||
* CO_ERROR_OUT_OF_MEMORY.
|
||||
*/
|
||||
CO_ReturnError_t CO_storageEeprom_init(CO_storage_t* storage, CO_CANmodule_t* CANmodule, void* storageModule,
|
||||
OD_entry_t* OD_1010_StoreParameters, OD_entry_t* OD_1011_RestoreDefaultParam,
|
||||
CO_storage_entry_t* entries, uint8_t entriesCount, uint32_t* storageInitError);
|
||||
|
||||
/**
|
||||
* Automatically update data if differs inside eeprom.
|
||||
*
|
||||
* Should be called cyclically by program. Each interval it updates one byte.
|
||||
*
|
||||
* @param storage This object
|
||||
* @param saveAll If true, all bytes are updated, useful on program end.
|
||||
*/
|
||||
void CO_storageEeprom_auto_process(CO_storage_t* storage, bool_t saveAll);
|
||||
|
||||
/** @} */ /* CO_storage_eeprom */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE */
|
||||
|
||||
#endif /* CO_STORAGE_EEPROM_H */
|
||||
Reference in New Issue
Block a user