/** * @file sdl.c * * @copyright Nehemis SARL reserves all rights even in the event of industrial * property rights. We reserve all rights of disposal such as * copying and passing on to third parties. * * @brief This file contains the initialization and execution logic for * the Valve Controller and CANopen stack. It defines the main * scheduler loop for running these components within the firmware. * * @todo SDL_MIN_OPERATIONAL_NODES to be changed to 20, are the node is fixed. * (electronic issue in the board) * */ /****************************************************************************** * Include Header Files ******************************************************************************/ /* CANopen includes */ #include #include #include #include "co_sdo.h" #include "co_odaccess.h" /* User includes */ #include "sdl.h" #include "processBoard.h" #include "enduranceTestBench.h" #include "hal_system.h" #include "stdlib.h" /****************************************************************************** * Macro constant declarations ******************************************************************************/ #define SDL_CANOPEN_BITRATE 250u #define SDL_PU_MASTER_NODE_ID 1u #define SDL_LSS_TIMEOUT 20u /* Defined in stack examples */ #define SDL_LSS_CONFIG_TIMEOUT 1000u #define SDL_LSS_CONFIG_INTERVAL 4u #define SDL_APPL_TIMER_TIME 250000uL #define SDL_LSS_NODE_COUNT 20u #define SDL_LSS_NODE_HB_MS 1000uL #define SDL_LSS_NODE_STATE_RESET_INTERVAL 3u #define SDL_TEST_BENCH_SDO_TIMEOUT 1000u #define SDL_TEST_BENCH_STARTUP_TIMEOUT 10000uLL #define SDL_POSITION_SETPOINT_INDEX 0x6002 #define SDL_POSITION_SETPOINT_SUB_INDEX 0x0 #define SDL_POSITION_FEEDBACK_INDEX 0x6004 #define SDL_POSITION_FEEDBACK_SUB_INDEX 0x0 #define SDL_SDO_CLIENT_COB 0x1280 #define SDL_SDO_CLIENT_TO_SERVER_COB 0x600 #define SDL_SDO_SERVER_TO_CLIENT_COB 0x580 #define SDL_MIN_OPERATIONAL_NODES 18u /* To be 20, one node not in working state currently */ /****************************************************************************** * Type Declarations ******************************************************************************/ typedef struct { uint32 vendorId_u32; uint32 productId_u32; uint32 versionNbr_u32; uint32 serialNbr_u32; uint8 nodeId_u8; } SdlLssNodeInfo_t; /****************************************************************************** * Global Declarations ******************************************************************************/ static bool testBenchStarted_b; SdlLssNodeInfo_t var_gst; static CO_TIMER_T monitorTimer_gst; /**< application timer */ static bool monitorSleep_gb = CO_FALSE; /**< sleep flag */ static bool masterStarted_gb; /**< master started flag */ static const SdlLssNodeInfo_t nodeLookupTable_gast[SDL_LSS_NODE_COUNT] = { {0x319, 0x4d2, 0x1, 0x01, 0x5} , {0x319, 0x4d2, 0x1, 0x02, 0x6} , {0x319, 0x4d2, 0x1, 0x03, 0x7} , {0x319, 0x4d2, 0x1, 0x04, 0x8} , {0x319, 0x4d2, 0x1, 0x05, 0x9} , {0x319, 0x4d2, 0x1, 0x06, 0xA} , {0x319, 0x4d2, 0x1, 0x07, 0xB} , {0x319, 0x4d2, 0x1, 0x08, 0xC} , {0x319, 0x4d2, 0x1, 0x09, 0xD} , {0x319, 0x4d2, 0x1, 0x0A, 0xE} , {0x319, 0x4d2, 0x1, 0x0B, 0xF} , {0x319, 0x4d2, 0x1, 0x0C, 0x10}, {0x319, 0x4d2, 0x1, 0x0D, 0x11}, {0x319, 0x4d2, 0x1, 0x0E, 0x12}, {0x319, 0x4d2, 0x1, 0x0F, 0x13}, {0x319, 0x4d2, 0x1, 0x10, 0x14}, {0x319, 0x4d2, 0x1, 0x11, 0x15}, {0x319, 0x4d2, 0x1, 0x12, 0x16}, {0x319, 0x4d2, 0x1, 0x13, 0x17}, {0x319, 0x4d2, 0x1, 0x14, 0x18} }; static CO_NMT_STATE_T nodeNMTState_gaen[SDL_LSS_NODE_COUNT]; /****************************************************************************** * Static function Declarations ******************************************************************************/ static void SdlInitCanopen(void); static void SdlRunCanopen(void); static RET_T SdlOverwriteLoadIndication(uint8 index_u8); static void SdlLssIndCallback(CO_LSS_MASTER_SERVICE_T service_en, uint16 errorCode_u16, uint8 errorSpec_u8, uint32 *pIdentity_pu32); static void SdlLssHbMonitorCallback(uint8 nodeID_u8, CO_ERRCTRL_T state_en, CO_NMT_STATE_T nmtState_en); static void SdlLssResetNodesCallback(void *pData_pv); /**< callback of reset handler */ static uint8 SdlLssGetNodeIndex(uint8 nodeId_u8); static void SdlLssToggleMonitorFlag(void *pData); static void SdlLssNodeHandlerRun(void); static uint32 SdlLssConfigureNode(uint8 arrIndex_u8); static RET_T SdlLssSetSdoCobID(uint8 sdoNr_u8, uint8 nodeId_u8); /* Supress warnings for implicit declaration. * Need this function for overwriting the manual COB ids * for the TPDO. */ extern void userOverwriteCobIdSettings(void); static bool SdlAreAllNodesOperational(void); /****************************************************************************** * Public Function Definitions ******************************************************************************/ void SdlInit(void) { testBenchStarted_b = false; SdlInitCanopen(); } void SdlRun(void) { SdlRunCanopen(); SdlLssNodeHandlerRun(); if (SdlAreAllNodesOperational()) { EnduranceTestBenchRun(&testBenchStarted_b); if (testBenchStarted_b == true) { ProcessBoardRun(); } } } /****************************************************************************** * Private Function Declarations ******************************************************************************/ /** * @brief Init the CANopen stack. * */ static void SdlInitCanopen(void) { RET_T retval; HalSystemInit(); codrvHardwareInit(); retval = codrvCanInit(SDL_CANOPEN_BITRATE); retval = codrvTimerSetup(CO_TIMER_INTERVAL); /* CANopen Initialization */ retval = coCanOpenStackInit(SdlOverwriteLoadIndication); retval = coEventRegister_LSS_MASTER(SdlLssIndCallback); retval = coEventRegister_ERRCTRL(SdlLssHbMonitorCallback); /* Register SDO event handlers */ coEventRegister_SDO_CLIENT_READ(EnduranceTestBenchReadInd); coEventRegister_SDO_CLIENT_WRITE(EnduranceTestBenchWriteInd); /* enable CAN communication */ retval = codrvCanEnable(); retval = coTimerStart(&monitorTimer_gst, SDL_APPL_TIMER_TIME, SdlLssToggleMonitorFlag, NULL, CO_TIMER_ATTR_ROUNDUP_CYCLIC); //retval = coTimerStart(&nodeResetTimer_gst, (SDL_LSS_NODE_STATE_RESET_INTERVAL * 1000000uLL), SdlLssResetNodesCallback, NULL, CO_TIMER_ATTR_ROUNDUP_CYCLIC); coLssIdentifyNonConfiguredSlaves(SDL_LSS_CONFIG_TIMEOUT, SDL_LSS_CONFIG_INTERVAL); retval = coNmtStateReq(SDL_PU_MASTER_NODE_ID, CO_NMT_STATE_OPERATIONAL, CO_TRUE); } /** * @brief Runs the CANopen stack. * */ static void SdlRunCanopen(void) { coCommTask(); SdlLssNodeHandlerRun(); } /****************************************************************************** * Private Function Definitions ******************************************************************************/ /** * @brief Handler for the defined nodes in the network. * This function starts the configuration of a node if needed * * @return void * */ static void SdlLssNodeHandlerRun(void) { if (monitorSleep_gb != CO_TRUE) { for (uint8 i_u8 = 0u; i_u8 < SDL_LSS_NODE_COUNT; i_u8++) { if (nodeNMTState_gaen[i_u8] == CO_NMT_STATE_PREOP) { SdlLssConfigureNode(i_u8); } if (nodeNMTState_gaen[i_u8] == CO_NMT_STATE_OPERATIONAL) { /* Do nothing */ } } } monitorSleep_gb = CO_TRUE; } /** * @brief Load function for overwriing the TPDO COB id values. * * @param index_u8 Subindex parameter to point parameter area(unused) * * @return Error Code */ static RET_T SdlOverwriteLoadIndication(uint8 index_u8) { userOverwriteCobIdSettings(); return RET_OK; } /** * @brief This function is called if a new NMT state of a node, * is recordnized by the CANopen stack. * * @param nodeID_u8 Node id of the node that has registered a change. * state_en Error control state * nmtState_en NMT state of the particular node. * * @return Array index of the node in the network. * */ static uint8 SdlLssGetNodeIndex(uint8 nodeId_u8) { uint8 arrIndex_u8; /* find node ID array arrIndex_u8 */ for (arrIndex_u8 = 0u; arrIndex_u8 < SDL_LSS_NODE_COUNT; arrIndex_u8++) { if (nodeId_u8 == nodeLookupTable_gast[arrIndex_u8].nodeId_u8) { return arrIndex_u8; } } return NMS_UINT8_MAX; } /** * @brief This function is called if a new NMT state of a node, * is recordnized by the CANopen stack. * * @param nodeID_u8 Node id of the node that has registered a change. * state_en Error control state * nmtState_en NMT state of the particular node. * * @return Array index of the node in the network. * */ static void SdlLssToggleMonitorFlag(void *pData) { (void)pData; /* deactivate application sleep flag */ monitorSleep_gb = CO_FALSE; } /** * @brief Configure a specific node. * This function adds the defined node to heartbeat consumers, * and sets its heartbeat interval. * * @param arrIndex_u8 Index of the node in the array which is to be configured. * * @return Status of the operation. * */ static uint32 SdlLssConfigureNode(uint8 arrIndex_u8) { uint32 error_u32 = SDL_DEFAULT_ERROR; RET_T retVal_en = RET_INTERNAL_ERROR; /* Get the node ID from the lookup table using the index passed */ uint8 nodeId_u8 = nodeLookupTable_gast[arrIndex_u8].nodeId_u8; /* Add node to heartbeat consumers with +25% tolerance * Rationale - Allows some flexibility in detecting timeouts due to minor clock drifts, * bus delays, or transmission issues.*/ retVal_en = coHbConsumerSet(nodeId_u8, ((SDL_LSS_NODE_HB_MS / 100u * 25u) + SDL_LSS_NODE_HB_MS)); retVal_en = coHbConsumerStart(nodeId_u8); /* setup SDO channel */ retVal_en = SdlLssSetSdoCobID((arrIndex_u8 + 1u), nodeLookupTable_gast[arrIndex_u8].nodeId_u8); /* start node */ retVal_en = coNmtStateReq(nodeId_u8, CO_NMT_STATE_OPERATIONAL, CO_FALSE); if (retVal_en != RET_OK) { error_u32 = SDL_LSS_NODE_CONFIG_ERROR; } /* set local state to operational, if not operational yet */ if (masterStarted_gb == CO_FALSE) { /* set local state */ coNmtLocalStateReq(CO_NMT_STATE_OPERATIONAL); /* save started flag */ masterStarted_gb = CO_TRUE; } return error_u32; } /** * @brief This function configures a SDO client for the * given node ID and SDO number. * * @param sdoNr_u8 Sdo number * nodeId_u8 Node id of the node. * * @return Error state. * */ static RET_T SdlLssSetSdoCobID(uint8 sdoNr_u8, uint8 nodeId_u8) { uint16 cobIndex_u16; uint32 newCobId_u32; RET_T retVal_en = RET_INTERNAL_ERROR; /* get od index of sdoNr */ cobIndex_u16 = SDL_SDO_CLIENT_COB + sdoNr_u8 - 1u; /* SDO starts from 1280 index */ /* set cobID for client to server direction (request) */ newCobId_u32 = SDL_SDO_CLIENT_TO_SERVER_COB + nodeId_u8; retVal_en = coOdSetCobid(cobIndex_u16, 1u, newCobId_u32); if (retVal_en == RET_OK) { /* set cobID for server to client direction (response) */ newCobId_u32 = SDL_SDO_SERVER_TO_CLIENT_COB + nodeId_u8; retVal_en = coOdSetCobid(cobIndex_u16, 2u, newCobId_u32); } /* print failed setup details */ if (retVal_en != RET_OK) { /* ERROR */ } return retVal_en; } /** * @brief Checks if all nodes are in OPERATIONAL state. * * @return CO_TRUE if all nodes are OPERATIONAL, CO_FALSE otherwise. */ static bool SdlAreAllNodesOperational(void) { uint8 operationalNodeNb_u8 = 0u; for (uint8 i_u8 = 0u; i_u8 < SDL_LSS_NODE_COUNT ; i_u8++) { if (nodeNMTState_gaen[i_u8] == CO_NMT_STATE_OPERATIONAL) { operationalNodeNb_u8++; } } return (operationalNodeNb_u8 >= SDL_MIN_OPERATIONAL_NODES) ? CO_TRUE : CO_FALSE; } /****************************************************************************** * Callback Function Definitions ******************************************************************************/ /** * @brief LSS indication function for handling the LSS api calls for dynamic * setup of node ids. * * @param service_en LSS master services for indication functions * errorCode_u16 Error code in the module * errorSpec_u8 Specific error case that has occured * pIdentity_pu32 LSS slave identity. * */ static void SdlLssIndCallback(CO_LSS_MASTER_SERVICE_T service_en, uint16 errorCode_u16, uint8 errorSpec_u8, uint32 *pIdentity_pu32) { static uint8 matchedIndex_u8 = NMS_UINT8_MAX; if (errorCode_u16 != 0u) { if (errorCode_u16 == NMS_UINT16_MAX) { /* ERROR */ } else { /* ERROR */ } if (service_en == CO_LSS_MASTER_SERVICE_STORE) { /* DEBUG INFO */ coLssSwitchGlobal(CO_LSS_STATE_WAITING); } return; } switch (service_en) { case CO_LSS_MASTER_SERVICE_NON_CONFIG_SLAVE: /* DEBUG INFO */ coLssFastScan(SDL_LSS_TIMEOUT); break; case CO_LSS_MASTER_SERVICE_FASTSCAN: /* Match detected node with lookup table */ for (uint8 i_u8 = 0u; i_u8 < SDL_LSS_NODE_COUNT; i_u8++) { if (pIdentity_pu32[0] == nodeLookupTable_gast[i_u8].vendorId_u32 && pIdentity_pu32[1] == nodeLookupTable_gast[i_u8].productId_u32 && pIdentity_pu32[2] == nodeLookupTable_gast[i_u8].versionNbr_u32 && pIdentity_pu32[3] == nodeLookupTable_gast[i_u8].serialNbr_u32) { coLssSwitchSelective(pIdentity_pu32[0], pIdentity_pu32[1], pIdentity_pu32[2], pIdentity_pu32[3], 20); matchedIndex_u8 = i_u8; break; } } break; case CO_LSS_MASTER_SERVICE_SWITCH_SELECTIVE: if (matchedIndex_u8 < SDL_LSS_NODE_COUNT) { coLssSetNodeId(nodeLookupTable_gast[matchedIndex_u8].nodeId_u8, SDL_LSS_TIMEOUT); matchedIndex_u8 = NMS_UINT8_MAX; } else { /* ERROR */ } break; case CO_LSS_MASTER_SERVICE_SET_NODEID: /* DEBUG INFO */ coLssInquireNodeId(SDL_LSS_TIMEOUT); break; case CO_LSS_MASTER_SERVICE_INQUIRE_NODEID: /* DEBUG INFO */ coLssStoreConfig(200); break; case CO_LSS_MASTER_SERVICE_STORE: /* DEBUG INFO */ coLssSwitchGlobal(CO_LSS_STATE_WAITING); break; default: /* ERROR */ break; } } /** * @brief This function is called if a new NMT state of a node, * is recordnized by the CANopen stack. * * @param nodeID_u8 Node id of the node that has registered a change. * state_en Error control state * nmtState_en NMT state of the particular node. * * @return void * */ static void SdlLssHbMonitorCallback(uint8 nodeID_u8, CO_ERRCTRL_T state_en, CO_NMT_STATE_T nmtState_en) { uint8 arrIndex_u8; /* look if node is monitored */ arrIndex_u8 = SdlLssGetNodeIndex(nodeID_u8); /* handle monitored node */ if (arrIndex_u8 != NMS_UINT16_MAX) { /* save states */ nodeNMTState_gaen[arrIndex_u8] = nmtState_en; /* indicate if monitored node lost heartbeat */ if (nmtState_en == CO_NMT_STATE_UNKNOWN) { /* To be transmitted via CAN */ /* ERROR */ } /* indicate if monitored node sent a bootup message */ if (state_en == CO_ERRCTRL_BOOTUP) { /* INFO */ } /* handle unmonitored node */ } else { /* ERROR */ } } /** * @brief This function tries to reset nodes with unknown NMT state. * * @param pData_pv Data. */ static void SdlLssResetNodesCallback(void *pData_pv) { uint8 arrIndex_u8; (void)pData_pv; /* reset defined nodes without known state */ for (arrIndex_u8 = 0u; arrIndex_u8 < SDL_LSS_NODE_COUNT; arrIndex_u8++) { if ((nodeNMTState_gaen[arrIndex_u8] != CO_NMT_STATE_PREOP) && (nodeNMTState_gaen[arrIndex_u8] != CO_NMT_STATE_OPERATIONAL)) { /* reset node */ coNmtStateReq(nodeLookupTable_gast[arrIndex_u8].nodeId_u8, CO_NMT_STATE_RESET_NODE, CO_FALSE); } } }