From f1674aec4e6ae5de40197a8a8b7c4ebd6b8c1e8c Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 13 Jul 2021 10:23:17 +0200 Subject: [PATCH] added SPI code --- CMakeLists.txt | 23 ++++- bsp_stm32h7_freertos/core/InitMission.cpp | 84 +++++++++++++++---- bsp_stm32h7_freertos/core/ObjectFactory.cpp | 68 +++++++++++---- .../fsfwconfig/OBSWConfig.h.in | 19 +++-- .../fsfwconfig/devices/devAddresses.h | 14 ++++ .../fsfwconfig/objects/systemObjectList.h | 4 + .../pollingSequenceFactory.cpp | 28 +++++++ .../pollingsequence/pollingSequenceFactory.h | 3 +- example_common | 2 +- fsfw_hal | 2 +- 10 files changed, 205 insertions(+), 42 deletions(-) create mode 100644 bsp_stm32h7_freertos/fsfwconfig/devices/devAddresses.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 51c3abb..43d111c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ set(STM32_TOOLCHAIN_PATH $ENV{STM32_TOOLCHAIN_PATH}) set(TARGET_TRIPLET "arm-none-eabi") set(OS_FSFW freertos CACHE STRING "OS for the FSFW") set(STM32_ADD_NETWORKING_CODE ON) +set(FSFW_HAL_ADD_STM32H7 ON) # This call has to come before the project call set(CMAKE_TOOLCHAIN_FILE ${STM32_CMAKE_PATH}/cmake/stm32_gcc.cmake) @@ -33,7 +34,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) find_package(CMSIS COMPONENTS STM32H743ZI STM32H7_M7 RTOS REQUIRED) find_package(FreeRTOS COMPONENTS STM32H7 ARM_CM7 REQUIRED) -find_package(HAL COMPONENTS STM32H7M7 STM32H743ZI RCC GPIO UART TIM CORTEX ETH REQUIRED) +find_package(HAL COMPONENTS STM32H7M7 STM32H743ZI RCC GPIO UART TIM CORTEX ETH SPI DMA REQUIRED) find_package(LwIP REQUIRED) set(TGT_BSP "arm/stm32h743zi-nucleo") @@ -42,7 +43,14 @@ set(FREERTOS_NAMESPACE FreeRTOS::STM32::H7::M7) # These need to be set for the FSFW so the sources are compiled with the ABI flags # and to compile FreeRTOS first set(LIB_OS_NAME ${FREERTOS_NAMESPACE}::ARM_CM7) -set(FSFW_ADDITIONAL_LINK_LIBS CMSIS::STM32::H743ZI::M7) +# This target is used because a custom linker script is used +set(CMSIS_LINK_TARGET CMSIS::STM32::H743xx::M7) +set(FSFW_ADDITIONAL_LINK_LIBS ${CMSIS_LINK_TARGET}) +set(FSFW_HAL_LINK_LIBS + HAL::STM32::H7::M7::CORTEX + ${CMSIS_LINK_TARGET} + ${LIB_OS_NAME} +) # Set names and variables set(TARGET_NAME ${CMAKE_PROJECT_NAME}) @@ -58,6 +66,9 @@ set(BSP_PATH "bsp_stm32h7_freertos") set(COMMON_CONFIG_PATH "${COMMON_PATH}/config") set(FSFW_CONFIG_PATH "${BSP_PATH}/fsfwconfig") set(BSP_NUCLEO_PATH "${BSP_PATH}/NUCLEO-H743ZI") +set(BSP_NUCLEO_INC_PATH "${BSP_NUCLEO_PATH}/Inc") + +set(FSFW_HAL_ADDITIONAL_INC_PATHS ${BSP_NUCLEO_INC_PATH}) set(FSFW_ADDITIONAL_INC_PATHS "${COMMON_CONFIG_PATH}" @@ -98,6 +109,7 @@ add_executable(${TARGET_NAME}) # Add subdirectories add_subdirectory(${BSP_PATH}) add_subdirectory(${FSFW_PATH}) +add_subdirectory(${LIB_FSFW_HAL_PATH}) add_subdirectory(${COMMON_PATH}) ################################################################################ @@ -107,6 +119,7 @@ add_subdirectory(${COMMON_PATH}) # Add libraries for all sources. target_link_libraries(${TARGET_NAME} PRIVATE ${LIB_FSFW_NAME} + ${LIB_FSFW_HAL_NAME} ${FREERTOS_NAMESPACE}::Heap::4 ${FREERTOS_NAMESPACE}::ARM_CM7 HAL::STM32::H7::M7::RCC @@ -117,7 +130,9 @@ target_link_libraries(${TARGET_NAME} PRIVATE HAL::STM32::H7::M7::TIM HAL::STM32::H7::M7::TIMEx HAL::STM32::H7::M7::ETH - CMSIS::STM32::H743ZI::M7 + HAL::STM32::H7::M7::SPI + HAL::STM32::H7::M7::DMA + ${CMSIS_LINK_TARGET} CMSIS::STM32::H7::M7::RTOS STM32::NoSys STM32::Nano @@ -179,6 +194,8 @@ string(CONCAT POST_BUILD_COMMENT "${TARGET_STRING}" ) +stm32_add_linker_script(${TARGET_NAME} "PRIVATE" "${BSP_NUCLEO_PATH}/STM32H743ZITx_FLASH.ld") + include (${CMAKE_SCRIPT_PATH}/BuildType.cmake) set_build_type() diff --git a/bsp_stm32h7_freertos/core/InitMission.cpp b/bsp_stm32h7_freertos/core/InitMission.cpp index 82836c1..b06a720 100644 --- a/bsp_stm32h7_freertos/core/InitMission.cpp +++ b/bsp_stm32h7_freertos/core/InitMission.cpp @@ -1,21 +1,28 @@ #include "InitMission.h" #include "OBSWConfig.h" +#include "objects/systemObjectList.h" +#include "pollingsequence/pollingSequenceFactory.h" + +#include "mission/utility/TaskCreation.h" +#include "mission/assemblies/TestAssembly.h" + +#include "fsfw/returnvalues/HasReturnvaluesIF.h" +#include "fsfw/serviceinterface/ServiceInterface.h" +#include "fsfw/tasks/FixedTimeslotTaskIF.h" +#include "fsfw/tasks/PeriodicTaskIF.h" +#include "fsfw/tasks/TaskFactory.h" +#include "fsfw/devicehandlers/DeviceHandlerIF.h" +#include "fsfw/modes/HasModesIF.h" +#include "fsfw/modes/ModeMessage.h" + #include "FreeRTOS.h" -#include -#include -#include -#include -#include -#include -#include -#include - void InitMission::createTasks() { + TaskFactory* taskFactory = TaskFactory::instance(); #if OBSW_ADD_CORE_COMPONENTS == 1 /* TMTC Distribution */ - PeriodicTaskIF* distributerTask = TaskFactory::instance()->createPeriodicTask( + PeriodicTaskIF* distributerTask = taskFactory->createPeriodicTask( "DIST", 5, 1024 * 2, 0.2, nullptr); ReturnValue_t result = distributerTask->addComponent(objects::CCSDS_DISTRIBUTOR); if(result!=HasReturnvaluesIF::RETURN_OK) { @@ -30,7 +37,6 @@ void InitMission::createTasks() { task::printInitError("TM funnel", objects::TM_FUNNEL); } -#if OBSW_ADD_LWIP_COMPONENTS == 1 /* UDP bridge */ PeriodicTaskIF* udpBridgeTask = TaskFactory::instance()->createPeriodicTask( "UDP_UNIX_BRIDGE", 6, 1024 * 2, 0.2, nullptr); @@ -44,7 +50,6 @@ void InitMission::createTasks() { if(result != HasReturnvaluesIF::RETURN_OK) { task::printInitError("UDP polling task", objects::UDP_POLLING_TASK); } -#endif /* OBSW_ADD_LWIP_COMPONENTS == 1 */ PeriodicTaskIF* eventManagerTask = TaskFactory::instance()->createPeriodicTask( "EVENT_MGMT", 4, 1024 * 2, 0.1, nullptr); @@ -119,7 +124,7 @@ void InitMission::createTasks() { #if OBSW_ADD_DEVICE_HANDLER_DEMO == 1 FixedTimeslotTaskIF* testDevicesTask = TaskFactory::instance()->createFixedTimeslotTask( - "PST_TEST_TASK", 10, 1024 * 2, 1.0, nullptr); + "PST_TEST_TASK", 7, 1024 * 2, 1.0, nullptr); result = pst::pollingSequenceDevices(testDevicesTask); if(result != HasReturnvaluesIF::RETURN_OK) { #if FSFW_CPP_OSTREAM_ENABLED == 1 @@ -128,6 +133,15 @@ void InitMission::createTasks() { sif::printError("InitMission::createTasks: Test PST initialization faiedl!\n"); #endif } + PeriodicTaskIF* assemblyTask = taskFactory->createPeriodicTask("ASS_TASK", 3, + 1024, 2.0, nullptr); + if(assemblyTask == nullptr){ + task::printInitError("ASS_TASK", objects::TEST_ASSEMBLY); + } + result = assemblyTask->addComponent(objects::TEST_ASSEMBLY); + if(result != HasReturnvaluesIF::RETURN_OK) { + task::printInitError("ASS_TASK", objects::TEST_ASSEMBLY); + } #endif /* OBSW_ADD_DEVICE_HANDLER_DEMO == 1 */ PeriodicTaskIF* testTask = TaskFactory::instance()->createPeriodicTask( @@ -137,6 +151,19 @@ void InitMission::createTasks() { task::printInitError("Test Task", objects::TEST_TASK); } +#if OBSW_PERIPHERAL_PST == 1 + FixedTimeslotTaskIF* peripheralPst = TaskFactory::instance()->createFixedTimeslotTask( + "PST_PERIPHERAL_TASK", 9, 1024 * 2, 2.0, nullptr); + result = pst::pstPeripheralsTest(peripheralPst); + if(result != HasReturnvaluesIF::RETURN_OK) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "InitMission::createTasks: Test PST initialization failed!" << std::endl; +#else + sif::printError("InitMission::createTasks: Test PST initialization faiedl!\n"); +#endif + } +#endif + #if FSFW_CPP_OSTREAM_ENABLED == 1 sif::info << "Starting tasks.." << std::endl; #else @@ -146,10 +173,8 @@ void InitMission::createTasks() { #if OBSW_ADD_CORE_COMPONENTS == 1 distributerTask->startTask(); eventManagerTask->startTask(); -#if OBSW_ADD_LWIP_COMPONENTS == 1 udpBridgeTask->startTask(); udpPollingTask->startTask(); -#endif /* OBSW_ADD_LWIP_COMPONENTS == 1 */ #endif /* OBSW_ADD_CORE_COMPONENTS == 1 */ #if OBSW_ADD_PUS_STACK == 1 @@ -166,8 +191,13 @@ void InitMission::createTasks() { #if OBSW_ADD_DEVICE_HANDLER_DEMO == 1 testDevicesTask->startTask(); + assemblyTask->startTask(); #endif /* OBSW_ADD_DEVICE_HANDLER_DEMO == 1 */ +#if OBSW_PERIPHERAL_PST == 1 + peripheralPst->startTask(); +#endif /* OBSW_PERIPHERAL_PST == 1 */ + testTask->startTask(); #if FSFW_CPP_OSTREAM_ENABLED == 1 @@ -179,5 +209,29 @@ void InitMission::createTasks() { static_cast(xPortGetFreeHeapSize())); sif::printInfo("Tasks started..\n"); #endif + +#if OBSW_ADD_DEVICE_HANDLER_DEMO + HasModesIF* assembly = ObjectManager::instance()->get(objects::TEST_ASSEMBLY); + if (assembly == nullptr){ + return; + } +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::info << "Waiting 5 Seconds and then command Test Assembly to Normal, Dual" << std::endl; +#else + sif::printInfo("Waiting 5 Seconds and then command Test Assembly to Normal, Dual \n"); +#endif + + TaskFactory::delayTask(5000); + CommandMessage modeMessage; + ModeMessage::setModeMessage(&modeMessage, ModeMessage::CMD_MODE_COMMAND, + DeviceHandlerIF::MODE_NORMAL, TestAssembly::submodes::DUAL); +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::info << "Commanding Test Assembly to Normal, Dual" << std::endl; +#else + sif::printInfo("Commanding Test Assembly to Normal, Dual \n"); +#endif + MessageQueueSenderIF::sendMessage(assembly->getCommandQueue(), &modeMessage, + MessageQueueIF::NO_QUEUE); +#endif /* OBSW_ADD_DEVICE_HANDLER_DEMO */ } diff --git a/bsp_stm32h7_freertos/core/ObjectFactory.cpp b/bsp_stm32h7_freertos/core/ObjectFactory.cpp index 71d8281..5c28d6e 100644 --- a/bsp_stm32h7_freertos/core/ObjectFactory.cpp +++ b/bsp_stm32h7_freertos/core/ObjectFactory.cpp @@ -1,23 +1,32 @@ #include "ObjectFactory.h" #include "OBSWConfig.h" -#include +#include "devices/devAddresses.h" +#include "objects/systemObjectList.h" -#include -#include +#include "mission/utility/TmFunnel.h" +#include "mission/core/GenericFactory.h" -#if OBSW_ADD_LWIP_COMPONENTS == 1 #include "example_common/stm32h7/networking/UdpTcLwIpPollingTask.h" #include "example_common/stm32h7/networking/TmTcLwIpUdpBridge.h" -#endif /* OBSW_ADD_LWIP_COMPONENTS == 1 */ - #include "example_common/stm32h7/STM32TestTask.h" -#include -#include -#include -#include -#include -#include +#include "fsfw/datapoollocal/LocalDataPoolManager.h" +#include "fsfw/monitoring/MonitoringMessageContent.h" +#include "fsfw/storagemanager/PoolManager.h" +#include "fsfw/tmtcpacket/pus/tm.h" +#include "fsfw/tmtcservices/CommandingServiceBase.h" +#include "fsfw/tmtcservices/PusServiceBase.h" + + +#if OBSW_PERFORM_L3GD20H_TEST == 1 +#include "fsfw_hal/stm32h7/spi/SpiCookie.h" +#include "fsfw_hal/stm32h7/spi/SpiComIF.h" +#include "fsfw_hal/devicehandlers/GyroL3GD20Handler.h" +#include "fsfw_hal/stm32h7/spi/stm32h743ziSpi.h" +#include "fsfw_hal/stm32h7/spi/spiCore.h" +DMA_HandleTypeDef* txHandle = nullptr; +DMA_HandleTypeDef* rxHandle = nullptr; +#endif void ObjectFactory::produce(void* args) { /* Located inside GenericFactory source file */ @@ -45,16 +54,47 @@ void ObjectFactory::produce(void* args) { new PoolManager(objects::IPC_STORE, poolCfg); } -#if OBSW_ADD_LWIP_COMPONENTS == 1 /* UDP Server */ new TmTcLwIpUdpBridge(objects::UDP_BRIDGE, objects::CCSDS_DISTRIBUTOR, objects::TM_STORE, objects::TC_STORE); new UdpTcLwIpPollingTask(objects::UDP_POLLING_TASK, objects::UDP_BRIDGE); -#endif /* OBSW_ADD_LWIP_COMPONENTS == 1 */ #endif /* OBSW_ADD_CORE_COMPONENTS == 1 */ ObjectFactory::produceGenericObjects(); /* Test Device Handler */ new STM32TestTask(objects::TEST_TASK, false, true); + +#if OBSW_PERFORM_L3GD20H_TEST == 1 + + spi::MspCfgBase* mspCfg = nullptr; + spi::TransferModes transferMode = spi::TransferModes::DMA; + if(transferMode == spi::TransferModes::POLLING) { + auto typedCfg = new spi::MspPollingConfigStruct(); + spi::h743zi::standardPollingCfg(*typedCfg); + mspCfg = typedCfg; + } + else if(transferMode == spi::TransferModes::INTERRUPT) { + auto typedCfg = new spi::MspIrqConfigStruct(); + spi::h743zi::standardInterruptCfg(*typedCfg, IrqPriorities::HIGHEST_FREERTOS); + mspCfg = typedCfg; + } + else if(transferMode == spi::TransferModes::DMA) { + auto typedCfg = new spi::MspDmaConfigStruct(); + txHandle = new DMA_HandleTypeDef(); + rxHandle = new DMA_HandleTypeDef(); + spi::setDmaHandles(txHandle, rxHandle); + spi::h743zi::standardDmaCfg(*typedCfg, IrqPriorities::HIGHEST_FREERTOS, + IrqPriorities::HIGHEST_FREERTOS, IrqPriorities::HIGHEST_FREERTOS); + mspCfg = typedCfg; + } + + new SpiComIF(objects::SPI_COM_IF); + auto spiCookie = new SpiCookie(devaddress::L3GD20H, spi::SpiBus::SPI_1, transferMode, mspCfg, + 3900000, spi::SpiModes::MODE_3, GPIO_PIN_14, GPIOD, 32); + auto gyroDevice = new GyroHandlerL3GD20H(objects::SPI_DEVICE_TEST, objects::SPI_COM_IF, + spiCookie); + gyroDevice->setStartUpImmediately(); + gyroDevice->setGoNormalModeAtStartup(); +#endif } diff --git a/bsp_stm32h7_freertos/fsfwconfig/OBSWConfig.h.in b/bsp_stm32h7_freertos/fsfwconfig/OBSWConfig.h.in index a1bd1b7..7e1dfcc 100644 --- a/bsp_stm32h7_freertos/fsfwconfig/OBSWConfig.h.in +++ b/bsp_stm32h7_freertos/fsfwconfig/OBSWConfig.h.in @@ -6,18 +6,23 @@ #ifndef FSFWCONFIG_OBSWCONFIG_H_ #define FSFWCONFIG_OBSWCONFIG_H_ -// Specify whether lwIP components are added. TMTC commanding is not possible without them +#define STM_USE_PERIPHERAL_TX_BUFFER_MPU_PROTECTION 1 + +//! Specify whether lwIP components are added. These are necessary for TMTC commanding #define OBSW_ADD_LWIP_COMPONENTS 1 - -// Specify whether TMTC commanding via Ethernet is possible +//! Specify whether TMTC commanding via Ethernet is possible #define OBSW_ETHERNET_TMTC_COMMANDING 1 - -// Only applies if TMTC commanding is enabled. -// Specify whether LEDs are used to display Ethernet connection status. +//! Only applies if TMTC commanding is enabled. +//! Specify whether LEDs are used to display Ethernet connection status. #define OBSW_ETHERNET_USE_LED1_LED2 0 #define OBSW_ATTEMPT_DHCP_CONN 1 +#define OBSW_PERIPHERAL_PST 0 +#define OBSW_PERFORM_SPI_TEST 0 + +#define OBSW_PERFORM_L3GD20H_TEST 0 + #if OBSW_ATTEMPT_DHCP_CONN == 0 #define MAX_DHCP_TRIES 0 #else @@ -29,7 +34,7 @@ #include "events/subsystemIdRanges.h" #include "objects/systemObjectList.h" -#include "commonConfig.h" +#include namespace config { #endif diff --git a/bsp_stm32h7_freertos/fsfwconfig/devices/devAddresses.h b/bsp_stm32h7_freertos/fsfwconfig/devices/devAddresses.h new file mode 100644 index 0000000..1ef7c6b --- /dev/null +++ b/bsp_stm32h7_freertos/fsfwconfig/devices/devAddresses.h @@ -0,0 +1,14 @@ +#ifndef BSP_STM32_FREERTOS_FSFWCONFIG_DEVICES_DEVADDRESSES_H_ +#define BSP_STM32_FREERTOS_FSFWCONFIG_DEVICES_DEVADDRESSES_H_ + +#include + +namespace devaddress { +enum devaddress: uint32_t { + L3GD20H = 1 +}; + +} + + +#endif /* BSP_STM32_FREERTOS_FSFWCONFIG_DEVICES_DEVADDRESSES_H_ */ diff --git a/bsp_stm32h7_freertos/fsfwconfig/objects/systemObjectList.h b/bsp_stm32h7_freertos/fsfwconfig/objects/systemObjectList.h index f86c899..4f47a45 100644 --- a/bsp_stm32h7_freertos/fsfwconfig/objects/systemObjectList.h +++ b/bsp_stm32h7_freertos/fsfwconfig/objects/systemObjectList.h @@ -8,6 +8,10 @@ enum mission_objects { /* 0x62 ('b') Board and mission specific objects */ UDP_BRIDGE = 0x62000300, UDP_POLLING_TASK = 0x62000400, + + SPI_COM_IF = 0x63001000, + SPI_DEVICE_TEST = 0x74001000, + /* Generic name for FSFW static ID setter */ DOWNLINK_DESTINATION = UDP_BRIDGE }; diff --git a/bsp_stm32h7_freertos/fsfwconfig/pollingsequence/pollingSequenceFactory.cpp b/bsp_stm32h7_freertos/fsfwconfig/pollingsequence/pollingSequenceFactory.cpp index e94b472..8dd121a 100644 --- a/bsp_stm32h7_freertos/fsfwconfig/pollingsequence/pollingSequenceFactory.cpp +++ b/bsp_stm32h7_freertos/fsfwconfig/pollingsequence/pollingSequenceFactory.cpp @@ -2,4 +2,32 @@ * Add polling sequence initialization which are not common to every BSP here. */ #include "pollingSequenceFactory.h" +#include "OBSWConfig.h" +#include +#include + +ReturnValue_t pst::pstPeripheralsTest(FixedTimeslotTaskIF *thisSequence) { + uint32_t length = thisSequence->getPeriodMs(); + static_cast(length); + +#if OBSW_PERFORM_L3GD20H_TEST == 1 + thisSequence->addSlot(objects::SPI_DEVICE_TEST, 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::SPI_DEVICE_TEST, 0.3, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::SPI_DEVICE_TEST, 0.45 * length, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::SPI_DEVICE_TEST, 0.6 * length, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::SPI_DEVICE_TEST, 0.8 * length, DeviceHandlerIF::GET_READ); +#endif + + if (thisSequence->checkSequence() == HasReturnvaluesIF::RETURN_OK) { + return HasReturnvaluesIF::RETURN_OK; + } +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "pst::pollingSequenceInitFunction: Initialization errors!" << std::endl; +#else + sif::printError("pst::pollingSequenceInitFunction: Initialization errors!\n"); +#endif + return HasReturnvaluesIF::RETURN_FAILED; + +} diff --git a/bsp_stm32h7_freertos/fsfwconfig/pollingsequence/pollingSequenceFactory.h b/bsp_stm32h7_freertos/fsfwconfig/pollingsequence/pollingSequenceFactory.h index b696b78..5776a29 100644 --- a/bsp_stm32h7_freertos/fsfwconfig/pollingsequence/pollingSequenceFactory.h +++ b/bsp_stm32h7_freertos/fsfwconfig/pollingsequence/pollingSequenceFactory.h @@ -2,11 +2,12 @@ #define POLLINGSEQUENCE_POLLINGSEQUENCFACTORY_H_ #include "OBSWConfig.h" -#include +#include "fsfw/returnvalues/HasReturnvaluesIF.h" class FixedTimeslotTaskIF; namespace pst { +ReturnValue_t pstPeripheralsTest(FixedTimeslotTaskIF *thisSequence); ReturnValue_t pollingSequenceExamples(FixedTimeslotTaskIF *thisSequence); ReturnValue_t pollingSequenceDevices(FixedTimeslotTaskIF* thisSequence); } diff --git a/example_common b/example_common index 1176959..e487913 160000 --- a/example_common +++ b/example_common @@ -1 +1 @@ -Subproject commit 1176959a938d9e41d3805f1e6abcfb124ee5953f +Subproject commit e4879130b09c76dac75265768fc757759e9aedcd diff --git a/fsfw_hal b/fsfw_hal index ee87546..9d20b88 160000 --- a/fsfw_hal +++ b/fsfw_hal @@ -1 +1 @@ -Subproject commit ee875460e7e00afdad21ffcb09f85ba7fcdaf1e3 +Subproject commit 9d20b8878e5422fadeee52b2543f8140aed04dc0