diff --git a/.gitignore b/.gitignore index 4c60d24..9620277 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ /example/canopennode_blank /canopend *.o -/od_storage* +*.persist +*.persist.old doc/html/ diff --git a/301/CO_Emergency.c b/301/CO_Emergency.c index 49704ab..8958108 100644 --- a/301/CO_Emergency.c +++ b/301/CO_Emergency.c @@ -474,8 +474,7 @@ CO_ReturnError_t CO_EM_init(CO_EM_t *em, em->OD_1015_extension.object = em; em->OD_1015_extension.read = OD_readOriginal; em->OD_1015_extension.write = OD_write_1015; - OD_extension_init(OD_1015_InhTime, - &em->OD_1015_extension); + 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 */ @@ -486,8 +485,7 @@ CO_ReturnError_t CO_EM_init(CO_EM_t *em, em->OD_1003_extension.object = em; em->OD_1003_extension.read = OD_read_1003; em->OD_1003_extension.write = OD_write_1003; - OD_extension_init(OD_1003_preDefErr, - &em->OD_1003_extension); + OD_extension_init(OD_1003_preDefErr, &em->OD_1003_extension); #endif /* (CO_CONFIG_EM) & CO_CONFIG_EM_HISTORY */ @@ -496,8 +494,7 @@ CO_ReturnError_t CO_EM_init(CO_EM_t *em, em->OD_statusBits_extension.object = em; em->OD_statusBits_extension.read = OD_read_statusBits; em->OD_statusBits_extension.write = OD_write_statusBits; - OD_extension_init(OD_statusBits, - &em->OD_statusBits_extension); + OD_extension_init(OD_statusBits, &em->OD_statusBits_extension); #endif /* (CO_CONFIG_EM) & CO_CONFIG_EM_STATUS_BITS */ diff --git a/301/CO_Emergency.h b/301/CO_Emergency.h index aa25655..c59df37 100644 --- a/301/CO_Emergency.h +++ b/301/CO_Emergency.h @@ -330,8 +330,8 @@ typedef enum { CO_EM_25_unused = 0x25U, /** 0x26, generic, info, (unused) */ CO_EM_26_unused = 0x26U, - /** 0x27, generic, info, (unused) */ - CO_EM_27_unused = 0x27U, + /** 0x27, generic, info, Automatic store to non-volatile memory failed */ + CO_EM_NON_VOLATILE_AUTO_SAVE = 0x27U, /** 0x28, generic, critical, Wrong parameters to CO_errorReport() function*/ CO_EM_WRONG_ERROR_REPORT = 0x28U, diff --git a/301/CO_NMT_Heartbeat.c b/301/CO_NMT_Heartbeat.c index 86a2d11..0cdbece 100644 --- a/301/CO_NMT_Heartbeat.c +++ b/301/CO_NMT_Heartbeat.c @@ -136,8 +136,7 @@ CO_ReturnError_t CO_NMT_init(CO_NMT_t *NMT, 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); + 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; diff --git a/301/CO_ODinterface.h b/301/CO_ODinterface.h index 6f88fda..f228aa5 100644 --- a/301/CO_ODinterface.h +++ b/301/CO_ODinterface.h @@ -75,8 +75,8 @@ typedef enum { OD_H100D_LIFETIME_FACTOR = 0x100DU,/**< Life time factor */ OD_H100E_RSV = 0x100EU,/**< Reserved */ OD_H100F_RSV = 0x100FU,/**< Reserved */ - OD_H1010_STORE_PARAM_FUNC = 0x1010U,/**< Store params in persistent mem.*/ - OD_H1011_REST_PARAM_FUNC = 0x1011U,/**< Restore default parameters */ + 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 */ diff --git a/301/CO_config.h b/301/CO_config.h index 1b82de1..701be25 100644 --- a/301/CO_config.h +++ b/301/CO_config.h @@ -460,6 +460,24 @@ extern "C" { /** @} */ /* CO_STACK_CONFIG_SYNC_PDO */ +/** + * @defgroup CO_STACK_CONFIG_STORAGE Data storage + * Data storage with CANopen OD objects 1010 and 1011 + * @{ + */ +/** + * 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 diff --git a/301/CO_storage.c b/301/CO_storage.c new file mode 100644 index 0000000..ca92bb6 --- /dev/null +++ b/301/CO_storage.c @@ -0,0 +1,214 @@ +/* + * CANopen data storage object + * + * @file CO_storage.c + * @author Janez Paternoster + * @copyright 2021 Janez Paternoster + * + * This file is part of CANopenNode, an opensource CANopen Stack. + * Project home page is . + * For more information on CANopen see . + * + * 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_storage.h" + +#if (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE + +/* + * Custom function for writing OD object "Store parameters" + * + * For more information see file CO_ODinterface.h, OD_IO_t. + */ +static OD_size_t OD_write_1010(OD_stream_t *stream, uint8_t subIndex, + const void *buf, OD_size_t count, + ODR_t *returnCode) +{ + /* verify arguments */ + if (stream == NULL || subIndex == 0 || buf == NULL || count != 4 + || returnCode == NULL + ) { + if (returnCode != NULL) *returnCode = ODR_DEV_INCOMPAT; + return 0; + } + + if (subIndex == 0) { + *returnCode = ODR_READONLY; + return 0; + } + + if (CO_getUint32(buf) != 0x65766173) { + *returnCode = ODR_DATA_TRANSF; + return 0; + } + + CO_storage_t *storage = stream->object; + CO_storage_entry_t *entry = storage->firstEntry; + bool_t found = false; + *returnCode = ODR_OK; + + /* call pre-configured function matching subIndex or call all functions */ + while (entry != NULL) { + if (entry->store != NULL + && (entry->subIndexOD == subIndex + || (storage->sub1_all && subIndex == 1)) + ) { + ODR_t code = entry->store(entry->object, entry->addr, entry->len); + found = true; + + if (code != ODR_OK) *returnCode = code; + if (!(storage->sub1_all && subIndex == 1)) { + break; + } + } + entry = entry->nextEntry; + } + + if (!found) *returnCode = ODR_SUB_NOT_EXIST; + + return *returnCode == ODR_OK ? 4 : 0; +} + + +/* + * Custom function for writing OD object "Restore default parameters" + * + * For more information see file CO_ODinterface.h, OD_IO_t. + */ +static OD_size_t OD_write_1011(OD_stream_t *stream, uint8_t subIndex, + const void *buf, OD_size_t count, + ODR_t *returnCode) +{ + /* verify arguments */ + if (stream == NULL || subIndex == 0 || buf == NULL || count != 4 + || returnCode == NULL + ) { + if (returnCode != NULL) *returnCode = ODR_DEV_INCOMPAT; + return 0; + } + + if (subIndex == 0) { + *returnCode = ODR_READONLY; + return 0; + } + + if (CO_getUint32(buf) != 0x64616F6C) { + *returnCode = ODR_DATA_TRANSF; + return 0; + } + + CO_storage_t *storage = stream->object; + CO_storage_entry_t *entry = storage->firstEntry; + bool_t found = false; + *returnCode = ODR_OK; + + /* call pre-configured function matching subIndex or call all functions */ + while (entry != NULL) { + if (entry->restore != NULL + && (entry->subIndexOD == subIndex + || (storage->sub1_all && subIndex == 1)) + ) { + ODR_t code = entry->restore(entry->object, entry->addr, entry->len); + found = true; + + if (code != ODR_OK) *returnCode = code; + if (!(storage->sub1_all && subIndex == 1)) { + break; + } + } + entry = entry->nextEntry; + } + + if (!found) *returnCode = ODR_SUB_NOT_EXIST; + + return *returnCode == ODR_OK ? 4 : 0; +} + + +CO_ReturnError_t CO_storage_init(CO_storage_t *storage, + OD_entry_t *OD_1010_StoreParameters, + OD_entry_t *OD_1011_RestoreDefaultParameters, + bool_t sub1_all, + uint32_t *errInfo) +{ + ODR_t odRet; + + /* verify arguments */ + if (storage == NULL || OD_1010_StoreParameters == NULL) { + return CO_ERROR_ILLEGAL_ARGUMENT; + } + + /* object was pre-initialised by CO_storage_pre_init() */ + + storage->sub1_all = sub1_all; + + /* configure extensions */ + storage->OD_1010_extension.object = storage; + storage->OD_1010_extension.read = OD_readOriginal; + storage->OD_1010_extension.write = OD_write_1010; + odRet = OD_extension_init(OD_1010_StoreParameters, + &storage->OD_1010_extension); + if (odRet != ODR_OK) { + if (errInfo != NULL) + *errInfo = OD_getIndex(OD_1010_StoreParameters); + return CO_ERROR_OD_PARAMETERS; + } + + if (OD_1011_RestoreDefaultParameters != NULL) { + storage->OD_1011_extension.object = storage; + storage->OD_1011_extension.read = OD_readOriginal; + storage->OD_1011_extension.write = OD_write_1011; + odRet = OD_extension_init(OD_1011_RestoreDefaultParameters, + &storage->OD_1011_extension); + if (odRet != ODR_OK) { + if (errInfo != NULL) + *errInfo = OD_getIndex(OD_1011_RestoreDefaultParameters); + return CO_ERROR_OD_PARAMETERS; + } + } + + return CO_ERROR_NO; +} + + +CO_ReturnError_t CO_storage_entry_init(CO_storage_t *storage, + CO_storage_entry_t *newEntry) { + /* verify arguments */ + if (storage == NULL || newEntry == NULL || newEntry->addr == NULL + || newEntry->len == 0 || newEntry->subIndexOD == 0 + ) { + return CO_ERROR_ILLEGAL_ARGUMENT; + } + + CO_storage_entry_t **entry = &storage->firstEntry; + + /* Add newEntry on the end of linked list or replace existing entry */ + for ( ; ; ) { + if (*entry == NULL) { + newEntry->nextEntry = NULL; + *entry = newEntry; + break; + } + if ((*entry)->subIndexOD == newEntry->subIndexOD) { + newEntry->nextEntry = (*entry)->nextEntry; + *entry = newEntry; + break; + } + *entry = (*entry)->nextEntry; + }; + + return CO_ERROR_NO; +} + +#endif /* (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE */ diff --git a/301/CO_storage.h b/301/CO_storage.h new file mode 100644 index 0000000..ac7eba8 --- /dev/null +++ b/301/CO_storage.h @@ -0,0 +1,175 @@ +/** + * CANopen data storage object + * + * @file CO_storage.h + * @ingroup CO_storage + * @author Janez Paternoster + * @copyright 2021 Janez Paternoster + * + * This file is part of CANopenNode, an opensource CANopen Stack. + * Project home page is . + * For more information on CANopen see . + * + * 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) || defined CO_DOXYGEN + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup CO_storage Data storage + * @ingroup CO_CANopen_301 + * @{ + * + * CANopen data storage - connection to Object Dictionary + */ + + +/** + * Data storage object for one entry. + * + * Object is defined by application and registered with + * @ref CO_storage_entry_init() function. + */ +typedef struct { + /** Address of data to store */ + void *addr; + /** Length of data to store */ + OD_size_t len; + /** Object defined by application, passed to store and restore functions. */ + void *object; + /** Sub index in OD objects 1010 and 1011, from 2 to 254. Writing + * 0x65766173 to 1010,subIndexOD will store data to non-volatile memory. + * Writing 0x64616F6C to 1011,subIndexOD will restore default data. */ + uint8_t subIndexOD; + /** + * Pointer to externally defined function, which will store data from addr. + * + * @param object object from above. + * @param addr Address form above. + * @param len Length from above. + * + * @return Value from @ref ODR_t, "ODR_OK" in case of success, "ODR_HW" in + * case of error in hardware. + */ + ODR_t (*store)(void *object, void *addr, OD_size_t len); + /** + * Pointer to externally defined function, which will restore data to addr. + * + * For description of arguments see above. + */ + ODR_t (*restore)(void *object, void *addr, OD_size_t len); + /** Pointer to next entry, initialized inside @ref CO_storage_entry_init() + * function. */ + void *nextEntry; +} CO_storage_entry_t; + + +/** + * Data storage object. + * + * Object is used with CANopen OD objects at index 1010 and 1011. + */ +typedef struct { + /** Extension for OD object */ + OD_extension_t OD_1010_extension; + /** Extension for OD object */ + OD_extension_t OD_1011_extension; + /** Null on the beginning, @ref CO_storage_entry_init() adds objects here + * and creates linked list. */ + CO_storage_entry_t *firstEntry; + /** From @ref CO_storage_init(). */ + bool_t sub1_all; +} CO_storage_t; + + +/** + * Pre-initialize data storage object. + * + * This function must be called before first @ref CO_storage_entry_init() + * function call. It is used inside @ref CO_new(). + * + * @param storage This object will be pre-initialized. + */ +static inline void CO_storage_pre_init(CO_storage_t *storage) { + if (storage != NULL) storage->firstEntry = NULL; +} + + +/** + * Initialize data storage object for usage with OD objects 1010 and 1011. + * + * Function is used inside @ref CO_CANopenInit(). It does not erase + * pre-configured entries from CO_storage_entry_init() calls. + * + * @param storage This object will be initialized. + * @param OD_1010_StoreParameters OD entry for 0x1010 -"Store parameters". + * @param OD_1011_RestoreDefaultParameters OD entry for 0x1011 -"Restore default + * parameters". Entry is optional, may be NULL. + * @param sub1_all If true, then writing to sub-index 1 of 1010 and 1011 will + * store/restore all data. This is default in CANopen. + * @param [out] errInfo Additional information in case of error, may be NULL. + * + * @return CO_ERROR_NO, CO_ERROR_ILLEGAL_ARGUMENT or CO_ERROR_OD_PARAMETERS. + */ +CO_ReturnError_t CO_storage_init(CO_storage_t *storage, + OD_entry_t *OD_1010_StoreParameters, + OD_entry_t *OD_1011_RestoreDefaultParameters, + bool_t sub1_all, + uint32_t *errInfo); + + +/** + * Initialize / add one entry into data storage object + * + * This function may be called by application one or several times after + * @ref CO_storage_pre_init() and before @ref CO_CANopenInit(). If entry with + * the same CO_storage_entry_t->subIndexOD as in newEntry already exists in + * storage object, then that entry in storage object will be replaced with + * newEntry. So it is not a problem to call this function multiple times with + * the same subIndexOD in CANopen communication reset section. + * + * @param storage Data storage object. + * @param newEntry New entry for data storage object. It must be initialized + * externally. This object must exist permanently. To disable entry, set store + * and restore function pointers to NULL. + * + * @return CO_ERROR_NO or CO_ERROR_ILLEGAL_ARGUMENT. + */ +CO_ReturnError_t CO_storage_entry_init(CO_storage_t *storage, + CO_storage_entry_t *newEntry); + + +/** @} */ /* CO_storage */ + +#ifdef __cplusplus +} +#endif /*__cplusplus*/ + +#endif /* (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE */ + +#endif /* CO_STORAGE_H */ diff --git a/CANopen.c b/CANopen.c index 2c14a52..e44aaba 100644 --- a/CANopen.c +++ b/CANopen.c @@ -185,6 +185,18 @@ #define CO_TX_CNT_TPDO 0 #endif +#if (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE + #if !defined OD_CNT_STORAGE + #define OD_CNT_STORAGE 0 + #define OD_ENTRY_H1010 NULL + #elif OD_CNT_STORAGE < 0 || OD_CNT_STORAGE > 1 + #error OD_CNT_STORAGE from OD.h not correct! + #endif + #ifndef OD_ENTRY_H1011 + #define OD_ENTRY_H1011 NULL + #endif +#endif + #if (CO_CONFIG_LEDS) & CO_CONFIG_LEDS_ENABLE #define OD_CNT_LEDS 1 #endif @@ -313,7 +325,8 @@ CO_t *CO_new(CO_config_t *config, uint32_t *heapMemoryUsed) { || config->CNT_EM > 1 || config->CNT_SDO_SRV > 128 || config->CNT_SDO_CLI > 128 || config->CNT_SYNC > 1 || config->CNT_RPDO > 512 || config->CNT_TPDO > 512 - || config->CNT_TIME > 1 || config->CNT_LEDS > 1 + || config->CNT_TIME > 1 + || config->OD_CNT_STORAGE > 1 || config->CNT_LEDS > 1 || config->CNT_GFC > 1 || config->CNT_SRDO > 64 || config->CNT_LSS_SLV > 1 || config->CNT_LSS_MST > 1 || config->CNT_GTWA > 1 @@ -456,6 +469,16 @@ CO_t *CO_new(CO_config_t *config, uint32_t *heapMemoryUsed) { } #endif +#if (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE + if (CO_GET_CNT(STORAGE) == 1) { + p = calloc(1, sizeof(CO_storage_t)); + if (p == NULL) break; + else co->storage = (CO_storage_t *)p; + CO_storage_pre_init(co->storage); + mem += sizeof(CO_storage_t); + } +#endif + #if (CO_CONFIG_LEDS) & CO_CONFIG_LEDS_ENABLE if (CO_GET_CNT(LEDS) == 1) { p = calloc(1, sizeof(CO_LEDs_t)); @@ -671,6 +694,10 @@ void CO_delete(CO_t *co) { free(co->GFC); #endif +#if (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE + free(co->storage); +#endif + #if (CO_CONFIG_LEDS) & CO_CONFIG_LEDS_ENABLE free(co->LEDs); #endif @@ -744,6 +771,9 @@ void CO_delete(CO_t *co) { #if (CO_CONFIG_PDO) & CO_CONFIG_TPDO_ENABLE static CO_TPDO_t COO_TPDO[OD_CNT_TPDO]; #endif +#if (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE + static CO_storage_t COO_storage; +#endif #if (CO_CONFIG_LEDS) & CO_CONFIG_LEDS_ENABLE static CO_LEDs_t COO_LEDs; #endif @@ -802,6 +832,9 @@ CO_t *CO_new(CO_config_t *config, uint32_t *heapMemoryUsed) { #if (CO_CONFIG_PDO) & CO_CONFIG_TPDO_ENABLE co->TPDO = &COO_TPDO[0]; #endif +#if (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE + co->storage = &COO_storage; +#endif #if (CO_CONFIG_LEDS) & CO_CONFIG_LEDS_ENABLE co->LEDs = &COO_LEDs; #endif @@ -945,6 +978,17 @@ CO_ReturnError_t CO_CANopenInit(CO_t *co, return CO_ERROR_ILLEGAL_ARGUMENT; } +#if (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE + if (CO_GET_CNT(STORAGE) == 1) { + err = CO_storage_init(co->storage, + OD_GET(H1010, OD_H1010_STORE_PARAMETERS), + OD_GET(H1011, OD_H1011_RESTORE_DEFAULT), + true, + errInfo); + if (err) return err; + } +#endif + #if (CO_CONFIG_LEDS) & CO_CONFIG_LEDS_ENABLE if (CO_GET_CNT(LEDS) == 1) { err = CO_LEDs_init(co->LEDs); diff --git a/CANopen.h b/CANopen.h index 093628a..4217652 100644 --- a/CANopen.h +++ b/CANopen.h @@ -31,57 +31,21 @@ #include "301/CO_driver.h" #include "301/CO_ODinterface.h" #include "301/CO_NMT_Heartbeat.h" - -#if ((CO_CONFIG_HB_CONS) & CO_CONFIG_HB_CONS_ENABLE) || defined CO_DOXYGEN #include "301/CO_HBconsumer.h" -#endif - #include "301/CO_Emergency.h" #include "301/CO_SDOserver.h" - -#if ((CO_CONFIG_SDO_CLI) & CO_CONFIG_SDO_CLI_ENABLE) || defined CO_DOXYGEN #include "301/CO_SDOclient.h" -#endif - -#if ((CO_CONFIG_SYNC) & CO_CONFIG_SYNC_ENABLE) || defined CO_DOXYGEN #include "301/CO_SYNC.h" -#endif - -#if ((CO_CONFIG_PDO) & (CO_CONFIG_RPDO_ENABLE | CO_CONFIG_TPDO_ENABLE)) || defined CO_DOXYGEN #include "301/CO_PDO.h" -#endif - -#if ((CO_CONFIG_TIME) & CO_CONFIG_TIME_ENABLE) || defined CO_DOXYGEN #include "301/CO_TIME.h" -#endif - -#if ((CO_CONFIG_LEDS) & CO_CONFIG_LEDS_ENABLE) || defined CO_DOXYGEN +#include "301/CO_storage.h" #include "303/CO_LEDs.h" -#endif - -#if ((CO_CONFIG_GFC) & CO_CONFIG_GFC_ENABLE) || defined CO_DOXYGEN #include "304/CO_GFC.h" -#endif - -#if ((CO_CONFIG_SRDO) & CO_CONFIG_SRDO_ENABLE) || defined CO_DOXYGEN #include "304/CO_SRDO.h" -#endif - -#if ((CO_CONFIG_LSS) & CO_CONFIG_LSS_SLAVE) || defined CO_DOXYGEN #include "305/CO_LSSslave.h" -#endif - -#if ((CO_CONFIG_LSS) & CO_CONFIG_LSS_MASTER) || defined CO_DOXYGEN #include "305/CO_LSSmaster.h" -#endif - -#if ((CO_CONFIG_GTW) & CO_CONFIG_GTW_ASCII) || defined CO_DOXYGEN #include "309/CO_gateway_ascii.h" -#endif - -#if ((CO_CONFIG_TRACE) & CO_CONFIG_TRACE_ENABLE) || defined CO_DOXYGEN #include "extra/CO_trace.h" -#endif #ifdef __cplusplus @@ -270,6 +234,10 @@ typedef struct { uint16_t CNT_TPDO; OD_entry_t *ENTRY_H1800; /**< OD entry for @ref CO_TPDO_init() */ OD_entry_t *ENTRY_H1A00; /**< OD entry for @ref CO_TPDO_init() */ + /** Number of storage objects, 0 or 1. */ + uint8_t CNT_STORAGE; + OD_entry_t *ENTRY_H1010; /**< OD entry for @ref CO_storage_init() */ + OD_entry_t *ENTRY_H1011; /**< OD entry for @ref CO_storage_init() */ /** Number of LEDs objects, 0 or 1. */ uint8_t CNT_LEDS; /** Number of GFC objects, 0 or 1 (CANrx + CANtx). */ @@ -375,6 +343,10 @@ typedef struct { uint16_t TX_IDX_TPDO; /**< Start index in CANtx. */ #endif #endif +#if ((CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE) || defined CO_DOXYGEN + /** Storage object, initialised by @ref CO_storage_init() */ + CO_storage_t *storage; +#endif #if ((CO_CONFIG_LEDS) & CO_CONFIG_LEDS_ENABLE) || defined CO_DOXYGEN /** LEDs object, initialised by @ref CO_LEDs_init() */ CO_LEDs_t *LEDs; diff --git a/Doxyfile b/Doxyfile index 064509f..86c9b45 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.8.17 +# Doxyfile 1.8.18 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -253,12 +253,6 @@ TAB_SIZE = 4 ALIASES = -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all @@ -300,13 +294,13 @@ OPTIMIZE_OUTPUT_SLICE = NO # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, JavaScript, -# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, # Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the -# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat -# .inc files as Fortran files (default is PHP), and .f files as C (default is -# Fortran), use: inc=Fortran f=C. +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # @@ -853,7 +847,7 @@ INPUT_ENCODING = UTF-8 # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, # *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), # *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen -# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd, +# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, # *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = @@ -1523,6 +1517,17 @@ TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png The default and svg Looks nicer but requires the +# pdf2svg tool. +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML @@ -1578,7 +1583,7 @@ MATHJAX_FORMAT = HTML-CSS # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from https://www.mathjax.org before deployment. -# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/. +# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest diff --git a/Makefile b/Makefile index 0e5be8c..4060f05 100644 --- a/Makefile +++ b/Makefile @@ -14,12 +14,12 @@ INCLUDE_DIRS = \ -I$(CANOPEN_SRC) \ -I$(APPL_SRC) -# $(DRV_SRC)/CO_OD_storage.c \ SOURCES = \ $(DRV_SRC)/CO_driver.c \ $(DRV_SRC)/CO_error.c \ $(DRV_SRC)/CO_epoll_interface.c \ + $(DRV_SRC)/CO_storageLinux.c \ $(CANOPEN_SRC)/301/CO_ODinterface.c \ $(CANOPEN_SRC)/301/CO_NMT_Heartbeat.c \ $(CANOPEN_SRC)/301/CO_HBconsumer.c \ @@ -31,6 +31,7 @@ SOURCES = \ $(CANOPEN_SRC)/301/CO_PDO.c \ $(CANOPEN_SRC)/301/crc16-ccitt.c \ $(CANOPEN_SRC)/301/CO_fifo.c \ + $(CANOPEN_SRC)/301/CO_storage.c \ $(CANOPEN_SRC)/303/CO_LEDs.c \ $(CANOPEN_SRC)/304/CO_GFC.c \ $(CANOPEN_SRC)/304/CO_SRDO.c \ diff --git a/README.md b/README.md index 30579ec..6057400 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,8 @@ Characteristics - [Suitable for 16-bit microcontrollers and above](#device-support) - [Multithreaded, real-time](#flowchart-of-a-typical-canopennode-implementation) - [Object Dictionary editor](#object-dictionary-editor) - - Non-volatile storage for Object Dictionary variables. + - Non-volatile storage for Object Dictionary or other variables. Automatic or + controlled by standard CANopen commands, configurable. - [Power saving possible](#power-saving) - [Bootloader possible](https://github.com/CANopenNode/CANopenNode/issues/111) (for firmware update) @@ -67,7 +68,7 @@ Further documented examples are available in [CANopenSocket](https://github.com/ Report issues on https://github.com/CANopenNode/CANopenNode/issues -Join discussion on Slack: https://canopennode.slack.com/ +For discussion on [Slack](https://canopennode.slack.com/) see: https://github.com/robincornelius/libedssharp Older discussion group is on Sourceforge: http://sourceforge.net/p/canopennode/discussion/387151/ diff --git a/doc/deviceSupport.md b/doc/deviceSupport.md index ab47409..0e9d4b1 100644 --- a/doc/deviceSupport.md +++ b/doc/deviceSupport.md @@ -96,6 +96,7 @@ S32DS (NXP S32 Design studio for Arm or Powerpc) Other ----- +* [ESP32](https://github.com/CANopenNode/CANopenNode/issues/198#issuecomment-658429391), 2020-07-14 * [FreeRTOS](https://github.com/martinwag/CANopenNode/tree/neuberger-freertos/stack/neuberger-FreeRTOS) by Neuberger, 2020-06-23, based on v1.3-master, see also [issue 198](https://github.com/CANopenNode/CANopenNode/issues/198). * [STM32CubeMX HAL](https://github.com/w1ne/CANOpenNode-CubeMX-HAL), 2019-05-03, demo project for Atollic studio, tested on Nucleo STM32L452xx board. * K64F_FreeRTOS, Kinetis SDK, 2018-02-13, [zip file](https://github.com/CANopenNode/CANopenNode/pull/28#issuecomment-365392867) diff --git a/doc/gettingStarted.md b/doc/gettingStarted.md index fa523a0..773ba1d 100644 --- a/doc/gettingStarted.md +++ b/doc/gettingStarted.md @@ -46,14 +46,14 @@ Go to the first terminal, where we have recently build executable, named _canope First print help, then run the program with some options. ./canopend --help - ./canopend vcan0 -i 4 #-s od4_storage -a od4_storage_auto + ./canopend vcan0 -i 4 You are now running a fully functional CANopen device on virtual CAN network. It is running in background until you terminate the process (with CTRL+C for example) or it receives a reset message from CAN network. By default process also shows some info messages on terminal, for example changes of NMT state or emergency messages, own and remote. On the second terminal you can see some CAN traffic. After _canopend_ startup, first messages are: vcan0 704 [1] 00 # Boot-up message. - vcan0 084 [8] 00 50 01 2F F3 FF FF FF # Emergency message. + vcan0 084 [8] 00 50 01 2F 03 00 00 00 # Emergency message. Boot-up message of node 4 have CAN-ID equal to 0x704. CAN-ID is 11-bit standard CAN identifier. @@ -63,11 +63,13 @@ The easiest way to find the reason of the emergency message is to check the byte This byte is CANopenNode specific. You can observe also first two bytes, which shows standard error code (0x5000 - Device Hardware) or third byte, which shows error register. If error register is different than zero, then node may be prohibited to enter operational and PDOs can not be exchanged with it. -You can follow the reason of the problem inside the source code. However, there are missing non-default storage files. Go to the first terminal, terminate the application with CTRL+C, add files and run _canopend_ again. +You can follow the reason of the problem inside the source code. However, data storage was not yet initialized, files are missing. CANopen device works, but NMT operational state is not possible. Data storage files on running new device can be generated by CANopen gateway command `4 write 0x1010 1 u32 0x65766173` or `4 write 0x1011 1 u32 0x64616F6C`. Or files can be generated manually with the Linux commands below. - echo "-" > od4_storage - echo "-" > od4_storage_auto - ./canopend vcan0 -i 4 #-s od4_storage -a od4_storage_auto +Go to the first terminal, terminate the application with CTRL+C, add files and run _canopend_ again. + + echo "-" > lss.persist + echo "-" > od_comm.persist + ./canopend vcan0 -i 4 Second terminal now shows new boot-up message without emergency. @@ -75,11 +77,11 @@ Second terminal now shows new boot-up message without emergency. ### Second CANopen device -Open the third terminal and cd to the same directory as is in the first terminal. First generate default storage files. Then start second instance of _canopend_ with NodeID = 1. Use default od_storage files and enable command interface on standard IO (terminal). +Open the third terminal and cd to the same directory as is in the first terminal. First generate data storage files with prefix, so data won't mix. Then start second instance of _canopend_ with NodeID = 1 and prefix for data storage. Enable command interface on standard IO (terminal). - echo "-" > od_storage - echo "-" > od_storage_auto - ./canopend vcan0 -i1 -c "stdio" + echo "-" > node1_lss.persist + echo "-" > node1_od_comm.persist + ./canopend vcan0 -i1 -s "node1_" -c "stdio" Now you should see in second terminal (_candump_) boot-up message of new CANopen device. diff --git a/doc/objectDictionary.md b/doc/objectDictionary.md index 5870021..400fb6d 100644 --- a/doc/objectDictionary.md +++ b/doc/objectDictionary.md @@ -458,7 +458,7 @@ Object Dictionary Requirements By CANopenNode {#object-dictionary-requirements-b | 100A | Manufacturer software version | | | | 100C | Guard time | | | | 100D | Life time factor | | | -| 1010 | Store parameters | | | +| 1010 | Store parameters | | STORAGE | | 1011 | Restore default parameters | | | | 1012 | COB-ID time stamp object | TIME, req | TIME | | 1013 | High resolution time stamp | | | diff --git a/example/DS301_profile.eds b/example/DS301_profile.eds index cae6850..2c92d40 100644 --- a/example/DS301_profile.eds +++ b/example/DS301_profile.eds @@ -8,8 +8,8 @@ Description= CreationTime=12:00PM CreationDate=11-23-2020 CreatedBy= -ModificationTime=4:21PM -ModificationDate=01-06-2021 +ModificationTime=3:01PM +ModificationDate=01-20-2021 ModifiedBy= [DeviceInfo] diff --git a/example/DS301_profile.md b/example/DS301_profile.md index 4196b2d..dde64d2 100644 --- a/example/DS301_profile.md +++ b/example/DS301_profile.md @@ -10,10 +10,10 @@ CANopen documentation | File Version | 1 | | Created | 23. 11. 2020 12:00:00 | | Created By | | -| Modified | 6. 01. 2021 16:21:52 | +| Modified | 20. 01. 2021 15:01:07 | | Modified By | | -This file was automatically generated with [libedssharp](https://github.com/robincornelius/libedssharp) Object Dictionary Editor v0.8-116-g2e8a14a +This file was automatically generated with [libedssharp](https://github.com/robincornelius/libedssharp) Object Dictionary Editor v0.8-114-gc3a898f * [Device Information](#device-information) * [PDO Mapping](#pdo-mapping) @@ -145,7 +145,7 @@ Synchronous window leghth in µs (0 = not used). All synchronous PDOs must be tr ### 0x1010 - Store parameters | Object Type | Count Label | Storage Group | | ----------- | -------------- | -------------- | -| ARRAY | | RAM | +| ARRAY | STORAGE | RAM | | Sub | Name | Data Type | SDO | PDO | SRDO | Default Value | | ---- | --------------------- | ---------- | --- | --- | ---- | ------------- | diff --git a/example/DS301_profile.xpd b/example/DS301_profile.xpd index 4ecbdc5..5e40227 100644 --- a/example/DS301_profile.xpd +++ b/example/DS301_profile.xpd @@ -1,5 +1,5 @@ - + @@ -15,7 +15,7 @@ CANopen - + @@ -753,6 +753,7 @@ * bit 0: If set, CANopen device saves parameters on command * Writing value 0x65766173 ('s','a','v','e' from LSB to MSB) stores corresponding data. + @@ -2253,7 +2254,7 @@ CANopen - + diff --git a/example/OD.c b/example/OD.c index 246ef5b..d8ec0d5 100644 --- a/example/OD.c +++ b/example/OD.c @@ -2,7 +2,7 @@ CANopen Object Dictionary definition for CANopenNode V4 This file was automatically generated with - libedssharp Object Dictionary Editor v0.8-116-g2e8a14a + libedssharp Object Dictionary Editor v0.8-114-gc3a898f https://github.com/CANopenNode/CANopenNode https://github.com/robincornelius/libedssharp diff --git a/example/OD.h b/example/OD.h index 7bcb955..b50db42 100644 --- a/example/OD.h +++ b/example/OD.h @@ -2,7 +2,7 @@ CANopen Object Dictionary definition for CANopenNode V4 This file was automatically generated with - libedssharp Object Dictionary Editor v0.8-116-g2e8a14a + libedssharp Object Dictionary Editor v0.8-114-gc3a898f https://github.com/CANopenNode/CANopenNode https://github.com/robincornelius/libedssharp @@ -17,7 +17,7 @@ Created: 23. 11. 2020 12:00:00 Created By: - Modified: 6. 01. 2021 16:21:52 + Modified: 20. 01. 2021 15:01:07 Modified By: Device Info: @@ -38,6 +38,7 @@ #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 diff --git a/socketCAN/CO_OD_storage.c b/socketCAN/CO_OD_storage.c deleted file mode 100644 index 1a39ee8..0000000 --- a/socketCAN/CO_OD_storage.c +++ /dev/null @@ -1,373 +0,0 @@ -/* - * CANopen Object Dictionary storage object for Linux SocketCAN. - * - * @file CO_OD_storage.c - * @author Janez Paternoster - * @copyright 2015 - 2020 Janez Paternoster - * - * This file is part of CANopenNode, an opensource CANopen Stack. - * Project home page is . - * For more information on CANopen see . - * - * 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_driver.h" -#include "301/CO_SDOserver.h" -#include "301/CO_Emergency.h" -#include "301/crc16-ccitt.h" -#include "CO_OD_storage.h" - -#include -#include /* for memcpy */ -#include /* for malloc, free */ - - -#define RETURN_SUCCESS 0 -#define RETURN_ERROR -1 - - -/******************************************************************************/ -CO_SDO_abortCode_t CO_ODF_1010(CO_ODF_arg_t *ODF_arg) { - CO_OD_storage_t *odStor; - uint32_t value; - CO_SDO_abortCode_t ret = CO_SDO_AB_NONE; - - odStor = (CO_OD_storage_t*) ODF_arg->object; - value = CO_getUint32(ODF_arg->data); - - if(!ODF_arg->reading) { - /* don't change the old value */ - memcpy(ODF_arg->data, ODF_arg->ODdataStorage, 4); - - if(ODF_arg->subIndex == 1) { - /* store parameters */ - if(value == 0x65766173UL) { - if(CO_OD_storage_saveSecure(odStor->odAddress, odStor->odSize, odStor->filename) != 0) { - ret = CO_SDO_AB_HW; - } - } - else { - ret = CO_SDO_AB_DATA_TRANSF; - } - } - } - - return ret; -} - - -/******************************************************************************/ -CO_SDO_abortCode_t CO_ODF_1011(CO_ODF_arg_t *ODF_arg) { - CO_OD_storage_t *odStor; - uint32_t value; - CO_SDO_abortCode_t ret = CO_SDO_AB_NONE; - - odStor = (CO_OD_storage_t*) ODF_arg->object; - value = CO_getUint32(ODF_arg->data); - - if(!ODF_arg->reading) { - /* don't change the old value */ - memcpy(ODF_arg->data, ODF_arg->ODdataStorage, 4); - - if(ODF_arg->subIndex >= 1) { - /* restore default parameters */ - if(value == 0x64616F6CUL) { - if(CO_OD_storage_restoreSecure(odStor->filename) != 0) { - ret = CO_SDO_AB_HW; - } - } - else { - ret = CO_SDO_AB_DATA_TRANSF; - } - } - } - - return ret; -} - - -/******************************************************************************/ -int CO_OD_storage_saveSecure( - uint8_t *odAddress, - uint32_t odSize, - char *filename) -{ - int ret = RETURN_SUCCESS; - - char *filename_old = NULL; - uint16_t CRC = 0; - - /* Generate new string with extension '.old' and rename current file to it. */ - filename_old = malloc(strlen(filename)+10); - if(filename_old != NULL) { - strcpy(filename_old, filename); - strcat(filename_old, ".old"); - - remove(filename_old); - if(rename(filename, filename_old) != 0) { - ret = RETURN_ERROR; - } - } else { - ret = RETURN_ERROR; - } - - /* Open a new file and write data to it, including CRC. */ - if(ret == RETURN_SUCCESS) { - FILE *fp = fopen(filename, "w"); - if(fp != NULL) { - - //CO_LOCK_OD(); - fwrite((const void *)odAddress, 1, odSize, fp); - CRC = crc16_ccitt((unsigned char*)odAddress, odSize, 0); - //CO_UNLOCK_OD(); - - fwrite((const void *)&CRC, 1, 2, fp); - fclose(fp); - } else { - ret = RETURN_ERROR; - } - } - - /* Verify data */ - if(ret == RETURN_SUCCESS) { - void *buf = NULL; - FILE *fp = NULL; - uint32_t cnt = 0; - uint16_t CRC2 = 0; - - buf = malloc(odSize + 4); - if(buf != NULL) { - fp = fopen(filename, "r"); - if(fp != NULL) { - cnt = fread(buf, 1, odSize, fp); - CRC2 = crc16_ccitt((unsigned char*)buf, odSize, 0); - /* read also two bytes of CRC */ - cnt += fread(buf, 1, 4, fp); - fclose(fp); - } - free(buf); - } - /* If size or CRC differs, report error */ - if(buf == NULL || fp == NULL || cnt != (odSize + 2) || CRC != CRC2) { - ret = RETURN_ERROR; - } - } - - /* In case of error, set back the old file. */ - if(ret != RETURN_SUCCESS && filename_old != NULL) { - remove(filename); - rename(filename_old, filename); - } - - free(filename_old); - - return ret; -} - - -/******************************************************************************/ -int CO_OD_storage_restoreSecure(char *filename) { - int ret = RETURN_SUCCESS; - FILE *fp = NULL; - - /* If filename already exists, rename it to '.old'. */ - fp = fopen(filename, "r"); - if(fp != NULL) { - char *filename_old = NULL; - - fclose(fp); - - filename_old = malloc(strlen(filename)+10); - if(filename_old != NULL) { - strcpy(filename_old, filename); - strcat(filename_old, ".old"); - - remove(filename_old); - if(rename(filename, filename_old) != 0) { - ret = RETURN_ERROR; - } - free(filename_old); - } - else { - ret = RETURN_ERROR; - } - } - - /* create an empty file and write "-\n" to it. */ - if(ret == RETURN_SUCCESS) { - fp = fopen(filename, "w"); - if(fp != NULL) { - fputs("-\n", fp); - fclose(fp); - } else { - ret = RETURN_ERROR; - } - } - - return ret; -} - - -/******************************************************************************/ -CO_ReturnError_t CO_OD_storage_init( - CO_OD_storage_t *odStor, - uint8_t *odAddress, - uint32_t odSize, - char *filename) -{ - CO_ReturnError_t ret = CO_ERROR_NO; - void *buf = NULL; - - /* verify arguments */ - if(odStor==NULL || odAddress==NULL) { - ret = CO_ERROR_ILLEGAL_ARGUMENT; - } - - /* configure object variables and allocate buffer */ - if(ret == CO_ERROR_NO) { - odStor->odAddress = odAddress; - odStor->odSize = odSize; - odStor->filename = filename; - odStor->fp = NULL; - odStor->lastSavedUs = 0; - - buf = malloc(odStor->odSize); - if(buf == NULL) { - ret = CO_ERROR_OUT_OF_MEMORY; - } - } - - /* read data from the file and verify CRC */ - if(ret == CO_ERROR_NO) { - FILE *fp; - uint32_t cnt = 0; - uint16_t CRC[2]; - - fp = fopen(odStor->filename, "r"); - if(fp) { - cnt = fread(buf, 1, odStor->odSize, fp); - /* read also two bytes of CRC from file */ - cnt += fread(&CRC[0], 1, 4, fp); - CRC[1] = crc16_ccitt((unsigned char*)buf, odStor->odSize, 0); - fclose(fp); - } - - if(cnt == 2 && *((char*)buf) == '-') { - /* file is empty, default values will be used, no error */ - ret = CO_ERROR_NO; - } - else if(cnt != (odStor->odSize + 2)) { - /* file length does not match */ - ret = CO_ERROR_DATA_CORRUPT; - } - else if(CRC[0] != CRC[1]) { - /* CRC does not match */ - ret = CO_ERROR_CRC; - } - else { - /* no errors, copy data into Object dictionary */ - memcpy(odStor->odAddress, buf, odStor->odSize); - } - } - - free(buf); - - return ret; -} - - -/******************************************************************************/ -CO_ReturnError_t CO_OD_storage_autoSave( - CO_OD_storage_t *odStor, - uint32_t timer1usDiff, - uint32_t delay_us) -{ - CO_ReturnError_t ret = CO_ERROR_NO; - - /* verify arguments */ - if(odStor==NULL || odStor->odAddress==NULL) { - ret = CO_ERROR_ILLEGAL_ARGUMENT; - } - - /* don't save file more often than delay */ - odStor->lastSavedUs += timer1usDiff; - if (odStor->lastSavedUs > delay_us) { - void *buf = NULL; - bool_t saveData = false; - - /* allocate buffer and open file if necessary */ - if(ret == CO_ERROR_NO) { - buf = malloc(odStor->odSize); - if(odStor->fp == NULL) { - odStor->fp = fopen(odStor->filename, "r+"); - } - if(buf == NULL || odStor->fp == NULL) { - ret = CO_ERROR_OUT_OF_MEMORY; - } - } - - /* read data from the beginning of the file */ - if(ret == CO_ERROR_NO) { - uint32_t cnt = 0; - - rewind(odStor->fp); - cnt = fread(buf, 1, odStor->odSize, odStor->fp); - - if(cnt == 2 && *((char*)buf) == '-') { - /* file is empty, data will be saved. */ - saveData = true; - } - else if(cnt == odStor->odSize) { - /* verify, if data differs */ - if(memcmp((const void *)buf, (const void *)odStor->odAddress, odStor->odSize) != 0) { - saveData = true; - } - } - else { - /* file length does not match */ - ret = CO_ERROR_DATA_CORRUPT; - } - } - - /* Save the data to the file only if data differs. */ - if(ret == CO_ERROR_NO && saveData) { - uint16_t CRC; - - /* copy data to temporary buffer */ - memcpy(buf, odStor->odAddress, odStor->odSize); - - rewind(odStor->fp); - fwrite((const void *)buf, 1, odStor->odSize, odStor->fp); - - /* write also CRC */ - CRC = crc16_ccitt((unsigned char*)buf, odStor->odSize, 0); - fwrite((const void *)&CRC, 1, 2, odStor->fp); - - fflush(odStor->fp); - - odStor->lastSavedUs = 0; - } - - free(buf); - } - - return ret; -} - -void CO_OD_storage_autoSaveClose(CO_OD_storage_t *odStor) { - if(odStor->fp != NULL) { - fclose(odStor->fp); - } -} diff --git a/socketCAN/CO_OD_storage.h b/socketCAN/CO_OD_storage.h deleted file mode 100644 index c0212e8..0000000 --- a/socketCAN/CO_OD_storage.h +++ /dev/null @@ -1,171 +0,0 @@ -/** - * CANopen Object Dictionary storage object for Linux SocketCAN. - * - * @file CO_OD_storage.h - * @ingroup CO_socketCAN_OD_storage - * @author Janez Paternoster - * @copyright 2015 - 2020 Janez Paternoster - * - * This file is part of CANopenNode, an opensource CANopen Stack. - * Project home page is . - * For more information on CANopen see . - * - * 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_STORAGE_H -#define CO_OD_STORAGE_H - - -#include "301/CO_driver.h" -#include "301/CO_SDOserver.h" - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @defgroup CO_socketCAN_OD_storage OD storage - * @ingroup CO_socketCAN - * @{ - * - * Object Dictionary storage implementation for CANopenNode on Linux - */ - - -/** - * Callback for use inside @ref CO_OD_configure() function for OD object 1010 - */ -CO_SDO_abortCode_t CO_ODF_1010(CO_ODF_arg_t *ODF_arg); - -/** - * Callback for use inside @ref CO_OD_configure() function for OD object 1011 - */ -CO_SDO_abortCode_t CO_ODF_1011(CO_ODF_arg_t *ODF_arg); - - -/** - * Save memory block to a file. - * - * Function renames current file to filename.old, copies contents from odAddress - * to filename, adds two bytes of CRC code. It then verifies the written file and - * in case of errors sets back the old file and returns error. - * - * Function is used with CANopen OD object at index 1010. - * - * @param odAddress Address of the memory block, which will be stored. - * @param odSize Size of the above memory block. - * @param filename Name of the file, where data will be stored. - * - * @return 0 on success, -1 on error. - */ -int CO_OD_storage_saveSecure( - uint8_t *odAddress, - uint32_t odSize, - char *filename); - - -/** - * Remove OD storage file. - * - * Function renames current file to filename.old, then creates empty file and - * writes two bytes "-\n" to it. When program will start next time, default values - * are used for Object Dictionary. In case of error in renaming to .old it - * keeps the original file and returns error. - * - * Writing data to file is secured with mutex CO_LOCK_OD. - * - * Function is used with CANopen OD object at index 1011. - * - * @param filename Name of the file. - * - * @return 0 on success, -1 on error. - */ -int CO_OD_storage_restoreSecure(char *filename); - - -/** - * Object Dictionary storage object. - * - * Object is used with CANopen OD objects at index 1010 and 1011. - */ -typedef struct { - uint8_t *odAddress; /**< From CO_OD_storage_init() */ - uint32_t odSize; /**< From CO_OD_storage_init() */ - char *filename; /**< From CO_OD_storage_init() */ - /** If CO_OD_storage_autoSave() is used, file stays opened and fp is stored here. */ - FILE *fp; - uint32_t lastSavedUs; /**< used with CO_OD_storage_autoSave. */ -} CO_OD_storage_t; - - -/** - * Initialize OD storage object and load data from file. - * - * Called after program startup. Load storage file and copy data to Object - * Dictionary variables. - * - * @param odStor This object will be initialized. - * @param odAddress Address of the memory block from Object dictionary, where data will be copied. - * @param odSize Size of the above memory block. - * @param filename Name of the file, where data are stored. - * - * @return #CO_ReturnError_t: CO_ERROR_NO, CO_ERROR_DATA_CORRUPT (Data in file corrupt), - * CO_ERROR_CRC (CRC from MBR does not match the CRC of OD_ROM block in file), - * CO_ERROR_ILLEGAL_ARGUMENT or CO_ERROR_OUT_OF_MEMORY (malloc failed). - */ -CO_ReturnError_t CO_OD_storage_init( - CO_OD_storage_t *odStor, - uint8_t *odAddress, - uint32_t odSize, - char *filename); - - -/** - * Automatically save memory block if differs from file. - * - * Should be called cyclically by program. It first verifies, if memory block - * differs from file and if it does, it saves it to file with two additional - * CRC bytes. File remains opened. - * - * @param odStor OD storage object. - * @param timer1usDiff Time difference in microseconds since last call. - * @param delay_us Delay (inhibit) time between writes to disk in microseconds - * (60000 for example). - * - * @return #CO_ReturnError_t: CO_ERROR_NO, CO_ERROR_DATA_CORRUPT (Data in file - * corrupt), CO_ERROR_ILLEGAL_ARGUMENT or CO_ERROR_OUT_OF_MEMORY (malloc failed). - */ -CO_ReturnError_t CO_OD_storage_autoSave( - CO_OD_storage_t *odStor, - uint32_t timer1usDiff, - uint32_t delay_us); - - -/** - * Closes file opened by CO_OD_storage_autoSave. - * - * @param odStor OD storage object. - */ -void CO_OD_storage_autoSaveClose(CO_OD_storage_t *odStor); - -/** @} */ - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* CO_OD_STORAGE_H */ diff --git a/socketCAN/CO_driver_target.h b/socketCAN/CO_driver_target.h index deacd2f..9003dad 100644 --- a/socketCAN/CO_driver_target.h +++ b/socketCAN/CO_driver_target.h @@ -261,7 +261,6 @@ extern "C" { #define CO_SWAP_64(x) bswap_64(x) #endif #endif -/* #define CO_USE_LEDS */ /* NULL is defined in stddef.h */ /* true and false are defined in stdbool.h */ /* int8_t to uint64_t are defined in stdint.h */ diff --git a/socketCAN/CO_main_basic.c b/socketCAN/CO_main_basic.c index 5a5cbc7..8c2d1b6 100644 --- a/socketCAN/CO_main_basic.c +++ b/socketCAN/CO_main_basic.c @@ -22,10 +22,6 @@ * limitations under the License. */ -#ifndef CO_OD_STORAGE -#define CO_OD_STORAGE 0 -#endif - #include #include #include @@ -46,9 +42,7 @@ #include "OD.h" #include "CO_error.h" #include "CO_epoll_interface.h" -#if CO_OD_STORAGE == 1 -#include "CO_OD_storage.h" -#endif +#include "CO_storageLinux.h" /* Call external application functions. */ #ifdef CO_USE_APPLICATION @@ -60,11 +54,6 @@ #include "CO_time_trace.h" #endif -/* Use DS309-3 standard - ASCII command interface to CANopen: NMT master, - * LSS master and SDO client */ -#if (CO_CONFIG_GTW) & CO_CONFIG_GTW_ASCII -#include "309/CO_gateway_ascii.h" -#endif /* Interval of mainline and real-time thread in microseconds */ #ifndef MAIN_THREAD_INTERVAL_US @@ -74,7 +63,7 @@ #define TMR_THREAD_INTERVAL_US 1000 #endif -/* default values */ +/* default values for CO_CANopenInit() */ #ifndef NMT_CONTROL #define NMT_CONTROL \ CO_NMT_STARTUP_TO_OPERATIONAL \ @@ -94,35 +83,82 @@ #ifndef SDO_CLI_BLOCK #define SDO_CLI_BLOCK false #endif -#ifndef GATEWAY_ENABLE -#define GATEWAY_ENABLE true -#endif #ifndef OD_STATUS_BITS #define OD_STATUS_BITS NULL #endif +/* CANopen gateway enable switch for CO_epoll_processMain() */ +#ifndef GATEWAY_ENABLE +#define GATEWAY_ENABLE true +#endif -/* Other variables and objects */ -#ifndef CO_SINGLE_THREAD -CO_epoll_t epRT; /* Epoll-timer object for realtime thread */ -static int rtPriority = -1; /* Real time priority, configurable by arguments. (-1=RT disabled) */ +/* CANopen object */ +CO_t *CO = NULL; + +/* Configurable CAN bit-rate and CANopen node-id, store-able to non-volatile + * memory. Can be set by argument and changed by LSS slave. */ +typedef struct { uint16_t bitRate; uint8_t nodeId; } CO_pending_t; +CO_pending_t CO_pending = { .bitRate = 0, .nodeId = CO_LSS_NODE_ID_ASSIGNMENT }; +/* Active node-id, copied from CO_pending.nodeId in the communication reset */ +static uint8_t CO_activeNodeId = CO_LSS_NODE_ID_ASSIGNMENT; + +/* Data storage for Object Dictionnary and LSS */ +#if (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE +/* Maximum file name length including path for storage */ +#ifndef CO_STORAGE_PATH_MAX +#define CO_STORAGE_PATH_MAX 255 #endif -CO_t *CO = NULL; /* CANopen object */ -static uint8_t CO_pendingNodeId = 0xFF; /* Set by arguments or by OD_CAN_NODE_ID macro, if defined. Can be changed by LSS slave. */ -static uint8_t CO_activeNodeId = 0xFF;/* Copied from CO_pendingNodeId in the communication reset section */ -static uint16_t CO_pendingBitRate = 0; /* CAN bitrate, not used here */ -#if CO_OD_STORAGE == 1 -static CO_OD_storage_t odStor; /* Object Dictionary storage object for CO_OD_ROM */ -static CO_OD_storage_t odStorAuto; /* Object Dictionary storage object for CO_OD_EEPROM */ -static char *odStorFile_rom = "od_storage"; /* Name of the file */ -static char *odStorFile_eeprom = "od_storage_auto"; /* Name of the file */ + +/* Definitions for application specific storage objects */ +#ifndef CO_STORAGE_APPLICATION +#define CO_STORAGE_APPLICATION #endif + +/* Interval for automatic storage in microseconds */ +#ifndef CO_STORAGE_AUTO_INTERVAL +#define CO_STORAGE_AUTO_INTERVAL 60000000 +#endif + +/* Configure objects for storage */ +typedef struct { + /* Address, length and filename of data to store */ + void *addr; + OD_size_t len; + char filename[CO_STORAGE_PATH_MAX]; + /* Subindex in OD objects 1010 and 1011 or 0 for auto storage. + * 1 is reserved for storing all parameters */ + uint8_t subIndexOD; + /* Object for storage or auto storage, usage depends on subIndexOD */ + CO_storage_entry_t entry; + CO_storageLinux_auto_t entryAuto; +} CO_storageLinux_entry_t; +CO_storageLinux_entry_t storage[] = { + { + .addr = &CO_pending, + .len = sizeof(CO_pending), + .filename = {'l', 's', 's', + '.', 'p', 'e', 'r', 's', 'i', 's', 't', '\0'}, + .subIndexOD = 0 + }, + { + .addr = &OD_PERSIST_COMM, + .len = sizeof(OD_PERSIST_COMM), + .filename = {'o', 'd', '_', 'c', 'o', 'm', 'm', + '.', 'p', 'e', 'r', 's', 'i', 's', 't', '\0'}, + .subIndexOD = 2 + }, + CO_STORAGE_APPLICATION +}; +#endif /* (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE */ + #if (CO_CONFIG_TRACE) & CO_CONFIG_TRACE_ENABLE static CO_time_t CO_time; /* Object for current time */ #endif + /* Helper functions ***********************************************************/ #ifndef CO_SINGLE_THREAD /* Realtime thread */ +CO_epoll_t epRT; static void* rt_thread(void* arg); #endif @@ -211,15 +247,13 @@ static void HeartbeatNmtChangedCallback(uint8_t nodeId, uint8_t idx, } #endif -#if CO_OD_STORAGE == 1 /* callback for storing node id and bitrate */ static bool_t LSScfgStoreCallback(void *object, uint8_t id, uint16_t bitRate) { (void)object; - OD_CANNodeID = id; - OD_CANBitRate = bitRate; + CO_pending.nodeId = id; + CO_pending.bitRate = bitRate; return true; } -#endif /* Print usage */ static void printUsage(char *progName) { @@ -236,11 +270,10 @@ printf( #endif printf( " -r Enable reboot on CANopen NMT reset_node command. \n"); -#if CO_OD_STORAGE == 1 +#if (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE printf( -" -s Set Filename for OD storage ('od_storage' is default).\n" -" -a Set Filename for automatic storage variables from\n" -" Object dictionary. ('od_storage_auto' is default).\n"); +" -s Path and filename prefix for data storage files.\n" +" By default files are stored in current dictionary.\n"); #endif #if (CO_CONFIG_GTW) & CO_CONFIG_GTW_ASCII printf( @@ -271,11 +304,13 @@ int main (int argc, char *argv[]) { CO_epoll_t epMain; #ifndef CO_SINGLE_THREAD pthread_t rt_thread_id; + int rtPriority = -1; #endif CO_NMT_reset_cmd_t reset = CO_RESET_NOT; CO_ReturnError_t err; -#if CO_OD_STORAGE == 1 - CO_ReturnError_t odStorStatus_rom, odStorStatus_eeprom; +#if (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE + uint32_t storageInitError = 0; + uint32_t storageIntervalTimer = 0; #endif CO_CANptrSocketCan_t CANptr = {0}; int opt; @@ -308,8 +343,8 @@ int main (int argc, char *argv[]) { while((opt = getopt(argc, argv, "i:p:rc:T:s:a:")) != -1) { switch (opt) { case 'i': - CO_pendingNodeId = (uint8_t)strtol(optarg, NULL, 0); nodeIdFromArgs = true; + CO_pending.nodeId = (uint8_t)strtol(optarg, NULL, 0); break; #ifndef CO_SINGLE_THREAD case 'p': rtPriority = strtol(optarg, NULL, 0); @@ -349,11 +384,21 @@ int main (int argc, char *argv[]) { socketTimeout_ms = strtoul(optarg, NULL, 0); break; #endif -#if CO_OD_STORAGE == 1 - case 's': odStorFile_rom = optarg; - break; - case 'a': odStorFile_eeprom = optarg; +#if (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE + case 's': { + /* add prefix to each storage[i].filename */ + for (int i = 0; i < sizeof(storage) / sizeof(storage[0]); i++) { + char* filePrefix = optarg; + size_t filePrefixLen = strlen(filePrefix); + char* file = storage[i].filename; + size_t fileLen = strlen(file); + if (fileLen + filePrefixLen < CO_STORAGE_PATH_MAX) { + memmove(&file[filePrefixLen], &file[0], fileLen + 1); + memcpy(&file[0], &filePrefix[0], filePrefixLen); + } + } break; + } #endif default: printUsage(argv[0]); @@ -366,18 +411,11 @@ int main (int argc, char *argv[]) { CANptr.can_ifindex = if_nametoindex(CANdevice); } - if(!nodeIdFromArgs) { -#ifdef OD_CAN_NODE_ID - /* use value from Object dictionary, if not set by program arguments */ - CO_pendingNodeId = OD_CAN_NODE_ID; -#endif - } - - if((CO_pendingNodeId < 1 || CO_pendingNodeId > 127) + if((CO_pending.nodeId < 1 || CO_pending.nodeId > 127) && CO_isLSSslaveEnabled(CO) - && CO_pendingNodeId != CO_LSS_NODE_ID_ASSIGNMENT + && CO_pending.nodeId != CO_LSS_NODE_ID_ASSIGNMENT ) { - log_printf(LOG_CRIT, DBG_WRONG_NODE_ID, CO_pendingNodeId); + log_printf(LOG_CRIT, DBG_WRONG_NODE_ID, CO_pending.nodeId); printUsage(argv[0]); exit(EXIT_FAILURE); } @@ -397,7 +435,7 @@ int main (int argc, char *argv[]) { } - log_printf(LOG_INFO, DBG_CAN_OPEN_INFO, CO_pendingNodeId, "starting"); + log_printf(LOG_INFO, DBG_CAN_OPEN_INFO, CO_pending.nodeId, "starting"); /* Allocate memory for CANopen objects */ @@ -410,25 +448,37 @@ int main (int argc, char *argv[]) { } -#if CO_OD_STORAGE == 1 - /* Verify, if OD structures have proper alignment of initial values */ - if(CO_OD_RAM.FirstWord != CO_OD_RAM.LastWord) { - log_printf(LOG_CRIT, DBG_OBJECT_DICTIONARY, "CO_OD_RAM"); - exit(EXIT_FAILURE); +#if (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE + uint8_t pendingNodeIdOriginal = CO_pending.nodeId; + for (int i = 0; i < sizeof(storage) / sizeof(storage[0]); i++) { + CO_storageLinux_entry_t *st = &storage[i]; + if (st->subIndexOD == 0) { + err = CO_storageLinux_auto_init(&st->entryAuto, + st->addr, + st->len, + st->filename); + } else { + err = CO_storageLinux_entry_init(CO->storage, + &st->entry, + st->addr, + st->len, + st->filename, + st->subIndexOD); + } + if (err == CO_ERROR_DATA_CORRUPT) { + uint32_t bit = 1; + if (i < 32) bit <<= i; + storageInitError |= bit; + } + else if (err != CO_ERROR_NO) { + log_printf(LOG_CRIT, DBG_OBJECT_DICTIONARY, st->filename); + exit(EXIT_FAILURE); + } } - if(CO_OD_EEPROM.FirstWord != CO_OD_EEPROM.LastWord) { - log_printf(LOG_CRIT, DBG_OBJECT_DICTIONARY, "CO_OD_EEPROM"); - exit(EXIT_FAILURE); + /* Overwrite stored node-id, if specified by program arguments */ + if (nodeIdFromArgs) { + CO_pending.nodeId = pendingNodeIdOriginal; } - if(CO_OD_ROM.FirstWord != CO_OD_ROM.LastWord) { - log_printf(LOG_CRIT, DBG_OBJECT_DICTIONARY, "CO_OD_ROM"); - exit(EXIT_FAILURE); - } - - - /* initialize Object Dictionary storage */ - odStorStatus_rom = CO_OD_storage_init(&odStor, (uint8_t*) &CO_OD_ROM, sizeof(CO_OD_ROM), odStorFile_rom); - odStorStatus_eeprom = CO_OD_storage_init(&odStorAuto, (uint8_t*) &CO_OD_EEPROM, sizeof(CO_OD_EEPROM), odStorFile_eeprom); #endif /* Catch signals SIGINT and SIGTERM */ @@ -502,7 +552,7 @@ int main (int argc, char *argv[]) { .serialNumber = OD_PERSIST_COMM.x1018_identity.serialNumber }}; err = CO_LSSinit(CO, &lssAddress, - &CO_pendingNodeId, &CO_pendingBitRate); + &CO_pending.nodeId, &CO_pending.bitRate); if(err != CO_ERROR_NO) { log_printf(LOG_CRIT, DBG_CAN_OPEN, "CO_LSSinit()", err); programExit = EXIT_FAILURE; @@ -510,7 +560,7 @@ int main (int argc, char *argv[]) { continue; } - CO_activeNodeId = CO_pendingNodeId; + CO_activeNodeId = CO_pending.nodeId; errInfo = 0; err = CO_CANopenInit(CO, /* CANopen object */ @@ -542,10 +592,8 @@ int main (int argc, char *argv[]) { #if (CO_CONFIG_GTW) & CO_CONFIG_GTW_ASCII CO_epoll_initCANopenGtw(&epGtw, CO); #endif -#if CO_OD_STORAGE == 1 CO_LSSslave_initCfgStoreCallback(CO->LSSslave, NULL, LSScfgStoreCallback); -#endif if(!CO->nodeIdUnconfigured) { #if (CO_CONFIG_EM) & CO_CONFIG_EM_CONSUMER CO_EM_initCallbackRx(CO->em, EmergencyRxCallback); @@ -557,15 +605,10 @@ int main (int argc, char *argv[]) { CO_HBconsumer_initCallbackNmtChanged(CO->HBcons, NULL, HeartbeatNmtChangedCallback); #endif -#if CO_OD_STORAGE == 1 - /* initialize OD objects 1010 and 1011 and verify errors. */ - CO_OD_configure(CO->SDO[0], OD_H1010_STORE_PARAM_FUNC, CO_ODF_1010, (void*)&odStor, 0, 0U); - CO_OD_configure(CO->SDO[0], OD_H1011_REST_PARAM_FUNC, CO_ODF_1011, (void*)&odStor, 0, 0U); - if(odStorStatus_rom != CO_ERROR_NO) { - CO_errorReport(CO->em, CO_EM_NON_VOLATILE_MEMORY, CO_EMC_HARDWARE, (uint32_t)odStorStatus_rom); - } - if(odStorStatus_eeprom != CO_ERROR_NO) { - CO_errorReport(CO->em, CO_EM_NON_VOLATILE_MEMORY, CO_EMC_HARDWARE, (uint32_t)odStorStatus_eeprom + 1000); +#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 @@ -651,9 +694,25 @@ int main (int argc, char *argv[]) { app_programAsync(!CO->nodeIdUnconfigured, epMain.timeDifference_us); #endif -#if CO_OD_STORAGE == 1 - CO_OD_storage_autoSave(&odStorAuto, - epMain.timeDifference_us, 60000000); +#if (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE + /* don't save more often than interval */ + if (storageIntervalTimer < CO_STORAGE_AUTO_INTERVAL) { + storageIntervalTimer += epMain.timeDifference_us; + } + else { + storageIntervalTimer = 0; + for (int i = 0; i < sizeof(storage) / sizeof(storage[0]); i++) { + CO_storageLinux_entry_t *st = &storage[i]; + if (st->subIndexOD == 0) { + bool_t storageOK = CO_storageLinux_auto_process( + &st->entryAuto, false); + if(!storageOK) { + CO_errorReport(CO->em, CO_EM_NON_VOLATILE_AUTO_SAVE, + CO_EMC_HARDWARE, i); + } + } + } + } #endif } } /* while(reset != CO_RESET_APP */ @@ -673,10 +732,13 @@ int main (int argc, char *argv[]) { app_programEnd(); #endif -#if CO_OD_STORAGE == 1 - /* Store CO_OD_EEPROM */ - CO_OD_storage_autoSave(&odStorAuto, 0, 0); - CO_OD_storage_autoSaveClose(&odStorAuto); +#if (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE + for (int i = 0; i < sizeof(storage) / sizeof(storage[0]); i++) { + CO_storageLinux_entry_t *st = &storage[i]; + if (st->subIndexOD == 0) { + CO_storageLinux_auto_process(&st->entryAuto, true); + } + } #endif /* delete objects from memory */ diff --git a/socketCAN/CO_storageLinux.c b/socketCAN/CO_storageLinux.c new file mode 100644 index 0000000..36b6703 --- /dev/null +++ b/socketCAN/CO_storageLinux.c @@ -0,0 +1,321 @@ +/* + * CANopen Object Dictionary storage object for Linux SocketCAN. + * + * @file CO_storageLinux.c + * @author Janez Paternoster + * @copyright 2021 Janez Paternoster + * + * This file is part of CANopenNode, an opensource CANopen Stack. + * Project home page is . + * For more information on CANopen see . + * + * 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_storageLinux.h" +#include "301/crc16-ccitt.h" + +#include +#include +#include + +#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 storeLinux(void *object, void *addr, OD_size_t len) { + ODR_t ret = ODR_OK; + char *filename = object; + uint16_t crc_store; + + /* Create names for temporary and old file */ + size_t fn_len = strlen(filename) + 5; + char *filename_tmp = malloc(fn_len); + char *filename_old = malloc(fn_len); + if (filename_tmp == NULL || filename_old == NULL) { + ret = ODR_OUT_OF_MEM; + } + else { + strcpy(filename_tmp, filename); + strcpy(filename_old, filename); + strcat(filename_tmp, ".tmp"); + strcat(filename_old, ".old"); + } + + /* Open a temporary file and write data to it */ + if (ret == ODR_OK) { + FILE *fp = fopen(filename_tmp, "w"); + if (fp == NULL) { + ret = ODR_HW; + } + else { + CO_LOCK_OD(); + size_t cnt = fwrite(addr, 1, len, fp); + crc_store = crc16_ccitt(addr, len, 0); + CO_UNLOCK_OD(); + cnt += fwrite(&crc_store, 1, sizeof(crc_store), fp); + fclose(fp); + if (cnt != (len + sizeof(crc_store))) { + ret = ODR_HW; + } + } + } + + /* Verify data */ + if (ret == ODR_OK) { + uint8_t *buf = NULL; + FILE *fp = NULL; + size_t cnt = 0; + uint16_t crc_verify, crc_read; + + buf = malloc(len + 4); + if (buf != NULL) { + fp = fopen(filename_tmp, "r"); + if (fp != NULL) { + cnt = fread(buf, 1, len + 4, fp); + crc_verify = crc16_ccitt(buf, len, 0); + fclose(fp); + memcpy(&crc_read, &buf[len], sizeof(crc_read)); + } + free(buf); + } + /* If size or CRC differs, report error */ + if (buf == NULL || fp == NULL || cnt != (len + sizeof(crc_verify)) + || crc_store != crc_verify || crc_store != crc_read + ) { + ret = ODR_HW; + } + } + + /* rename existing file to *.old and *.tmp to existing */ + if (ret == ODR_OK) { + if (rename(filename, filename_old) != 0 + || rename(filename_tmp, filename) != 0 + ) { + ret = ODR_HW; + } + } + + free(filename_tmp); + free(filename_old); + + return ret; +} + + +/* + * 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 restoreLinux(void *object, void *addr, OD_size_t len) { + (void) addr; (void) len; + + char *filename = object; + ODR_t ret = ODR_OK; + + /* Rename existing filename to *.old. */ + char *filename_old = malloc(strlen(filename) + 5); + if (filename_old == NULL) { + ret = ODR_OUT_OF_MEM; + } + else { + strcpy(filename_old, filename); + strcat(filename_old, ".old"); + rename(filename, filename_old); + free(filename_old); + } + + /* create an empty file and write "-\n" to it. */ + if (ret == ODR_OK) { + FILE *fp = fopen(filename, "w"); + if (fp == NULL) { + ret = ODR_HW; + } + else { + fputs("-\n", fp); + fclose(fp); + } + } + + return ret; +} + + +CO_ReturnError_t CO_storageLinux_entry_init(CO_storage_t *storage, + CO_storage_entry_t *newEntry, + void *addr, + OD_size_t len, + char *filename, + uint8_t subIndexOD) +{ + /* verify arguments */ + if (storage == NULL || newEntry == NULL || addr == NULL || len == 0 + || filename == NULL || subIndexOD < 1 + ) { + return CO_ERROR_ILLEGAL_ARGUMENT; + } + + /* configure newEntry and add it to the storage */ + newEntry->addr = addr; + newEntry->len = len; + newEntry->object = filename; + newEntry->subIndexOD = subIndexOD; + newEntry->store = storeLinux; + newEntry->restore = restoreLinux; + + CO_ReturnError_t ret; + FILE *fp = NULL; + uint8_t *buf = NULL; + + /* Add newEntry to storage object */ + ret = CO_storage_entry_init(storage, newEntry); + + /* Open file, check existence and create temporary buffer */ + if (ret == CO_ERROR_NO) { + fp = fopen(filename, "r"); + if (fp == NULL) { + ret = CO_ERROR_DATA_CORRUPT; + } + else { + buf = malloc(len + 4); + if (buf == NULL) { + ret = CO_ERROR_OUT_OF_MEMORY; + } + } + } + + /* Read data into temporary buffer first. Then verify and copy to addr */ + if (ret == CO_ERROR_NO) { + size_t cnt = fread(buf, 1, len + 4, fp); + + uint16_t crc1, crc2; + crc1 = crc16_ccitt(buf, len, 0); + memcpy(&crc2, &buf[len], sizeof(crc2)); + + if (cnt == 2 && buf[0] == '-') { + /* File is empty, default values will be used, no error */ + } + else if (cnt != (len + sizeof(crc2)) || crc1 != crc2) { + /* Data length does not match */ + ret = CO_ERROR_DATA_CORRUPT; + } + else { + /* no errors, copy data into Object dictionary */ + memcpy(addr, buf, len); + } + } + + if (buf != NULL) free(buf); + if (fp != NULL) fclose(fp); + + return ret; +} + + +CO_ReturnError_t CO_storageLinux_auto_init(CO_storageLinux_auto_t *storageAuto, + void *addr, + OD_size_t len, + char *filename) +{ + /* verify arguments */ + if (storageAuto == NULL || addr == NULL || len == 0 || filename == NULL) { + return CO_ERROR_ILLEGAL_ARGUMENT; + } + + /* configure variables */ + storageAuto->addr = addr; + storageAuto->len = len; + storageAuto->crc = 0; + bool_t readOK = false; + char *writeFileAccess = "w"; + + /* Open the file, read data into temporary buffer, verify them and copy them + * into addr. If file does not exist or data is corrupt, just skip reading + * and keep the default values. */ + FILE *fp = fopen(filename, "r"); + if (fp != NULL) { + uint8_t *buf = malloc(len + 4); + if (buf != NULL) { + size_t cnt = fread(buf, 1, len + 4, fp); + + if (cnt == 2 && buf[0] == '-') { + /* File is empty, default values will be used, no error */ + readOK = true; + } + else { + uint16_t crc1, crc2; + crc1 = crc16_ccitt(buf, len, 0); + memcpy(&crc2, &buf[len], sizeof(crc2)); + + if (crc1 == crc2 && cnt == (len + sizeof(crc2))) { + memcpy(addr, buf, len); + storageAuto->crc = crc1; + readOK = true; + writeFileAccess = "r+"; + } + } + free(buf); + } + fclose(fp); + } + + storageAuto->fp = fopen(filename, writeFileAccess); + if (storageAuto->fp == NULL) return CO_ERROR_ILLEGAL_ARGUMENT; + + return readOK ? CO_ERROR_NO : CO_ERROR_DATA_CORRUPT; +} + + +bool_t CO_storageLinux_auto_process(CO_storageLinux_auto_t *storageAuto, + bool_t closeFile) +{ + bool_t ret = false; + + /* verify arguments */ + if (storageAuto == NULL || storageAuto->fp == NULL) { + return ret; + } + + /* If CRC of the current data differs, save the file */ + uint16_t crc = crc16_ccitt(storageAuto->addr, storageAuto->len, 0); + if (crc == storageAuto->crc) { + ret = true; + } + else { + size_t cnt; + rewind(storageAuto->fp); + CO_LOCK_OD(); + cnt = fwrite(storageAuto->addr, 1, storageAuto->len, storageAuto->fp); + CO_UNLOCK_OD(); + cnt += fwrite(&crc, 1, sizeof(crc), storageAuto->fp); + fflush(storageAuto->fp); + if (cnt == (storageAuto->len + sizeof(crc))) { + storageAuto->crc = crc; + ret = true; + } + } + + if (closeFile) { + fclose(storageAuto->fp); + storageAuto->fp = NULL; + } + + return ret; +} + +#endif /* (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE */ diff --git a/socketCAN/CO_storageLinux.h b/socketCAN/CO_storageLinux.h new file mode 100644 index 0000000..ccee474 --- /dev/null +++ b/socketCAN/CO_storageLinux.h @@ -0,0 +1,135 @@ +/** + * CANopen data storage object for Linux + * + * @file CO_storageLinux.h + * @ingroup CO_storageLinux + * @author Janez Paternoster + * @copyright 2021 Janez Paternoster + * + * This file is part of CANopenNode, an opensource CANopen Stack. + * Project home page is . + * For more information on CANopen see . + * + * 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_LINUX_H +#define CO_STORAGE_LINUX_H + +#include "301/CO_storage.h" +#include + +#if ((CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE) || defined CO_DOXYGEN + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup CO_storageLinux Data storage with Linux + * @ingroup CO_socketCAN + * @{ + * + * Data initialize, store and restore functions with Linux, see @ref CO_storage + */ + + +/** + * Initialize / add one entry into data storage object (Linux specific) + * + * This function configures newEntry and calls @ref CO_storage_entry_init(). + * Then reads data from file, verifies and writes them to addr. + * + * @param storage Data storage object. + * @param newEntry Data storage object for one entry. It will be configured and + * must exist permanently. + * @param addr Address of data to store + * @param len Length of data to store + * @param filename Name of the file, where data are stored. + * @param subIndexOD Sub index in OD 1010 and 1011 ,see @ref CO_storage_entry_t + * + * @return CO_ERROR_NO, CO_ERROR_DATA_CORRUPT if memory can not be initialized, + * CO_ERROR_ILLEGAL_ARGUMENT or CO_ERROR_OUT_OF_MEMORY. + */ +CO_ReturnError_t CO_storageLinux_entry_init(CO_storage_t *storage, + CO_storage_entry_t *newEntry, + void *addr, + OD_size_t len, + char *filename, + uint8_t subIndexOD); + + +/** + * Object for automatic data storage + */ +typedef struct { + /** Address of data to store */ + void *addr; + /** Length of data to store */ + OD_size_t len; + /** CRC checksum of the data stored previously. */ + uint16_t crc; + /** Pointer to file opened by @ref CO_storageLinux_auto_init() */ + FILE *fp; +} CO_storageLinux_auto_t; + + +/** + * Initialize automatic storage with Linux + * + * Function in combination with @ref CO_storageLinux_auto_process() are + * standalone functions. They are used for automatic data storage. + * + * This function configures storageAuto, then reads data from file. If data are + * valid, then it writes them to addr. Function also open file pointer for + * writing and keeps it open. + * + * @param storageAuto This object will be initialized. + * @param addr Address of data to store + * @param len Length of data to store + * @param filename Name of the file, where data are stored. + * + * @return CO_ERROR_NO, CO_ERROR_DATA_CORRUPT if memory can not be initialized, + * CO_ERROR_ILLEGAL_ARGUMENT, if file can not be opened for writing. + */ +CO_ReturnError_t CO_storageLinux_auto_init(CO_storageLinux_auto_t *storageAuto, + void *addr, + OD_size_t len, + char *filename); + + +/** + * Automatically save data if differs from previous call. + * + * Should be called cyclically by program. Each interval it verifies, if crc + * checksum of data differs previous checksum. If it does, data are saved into + * file, opened by CO_storageLinux_auto_init(). + * + * @param storageAuto This object + * @param closeFile If true, then data will be stored regardless interval and + * file will be closed. Use on end of program. + * + * @return true, if data are unchanged or saved successfully. + */ +bool_t CO_storageLinux_auto_process(CO_storageLinux_auto_t *storageAuto, + bool_t closeFile); + +/** @} */ /* CO_storageLinux */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* (CO_CONFIG_STORAGE) & CO_CONFIG_STORAGE_ENABLE */ + +#endif /* CO_STORAGE_LINUX_H */