第一版代码,为了在EEPROM保存参数的时候走STM32的CRC,让Codex修改了一下,现在的效果是无法存储,codex表示原因是CRC方法不同,修改到一半今天的额度使用完了,有待后续解决CRC的bug

This commit is contained in:
2026-02-28 17:36:05 +08:00
commit b2fedd58b2
212 changed files with 208290 additions and 0 deletions

View 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
}

View 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 */

View 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 */

View 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 */

View 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

View 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 */

View 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 */

View 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 */

View 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;
}

View 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 */

File diff suppressed because it is too large Load Diff

View 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 */

File diff suppressed because it is too large Load Diff

View 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 */

File diff suppressed because it is too large Load Diff

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

File diff suppressed because it is too large Load Diff

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

File diff suppressed because it is too large Load Diff

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

File diff suppressed because it is too large Load Diff

View 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(&gtwa->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(&gtwa->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 */

View File

@@ -0,0 +1,87 @@
#ifndef __CIA402_DEFS_H__
#define __CIA402_DEFS_H__
//本文件存放CiA402中INDEXcontrolwordstatusword的宏定义替换
#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

File diff suppressed because it is too large Load Diff

View 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 */

View 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);
}

View 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_ */

View 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

View 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 */

View 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 */

View 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 */

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

View 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 $@

File diff suppressed because it is too large Load Diff

View 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 */

File diff suppressed because it is too large Load Diff

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */