diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0753d2e..fbfb4831 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -113,6 +113,7 @@ jobs: # GitHub Actions does not allow the secrets context inside if expression # So, let's copy the secret into env env: + CUBEMX_ARCHIVE_REPO: ${{ github.repository }} LIBCPNODE_DEPLOY_KEY: ${{ secrets.LIBCPNODE_DEPLOY_KEY }} steps: @@ -163,6 +164,6 @@ jobs: with: bot_token: ${{ secrets.TELEGRAM_BOT_TOKEN }} chat_id: ${{ secrets.TELEGRAM_CHAT_ID }} - files: build/release/*.bin + files: release/*.bin message: "Node" add_git_info: "true" diff --git a/.gitignore b/.gitignore index 0ce5aeb3..36b1251e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ build/ build_dsdl/ +/release/* .vscode/ .venv/ __pycache__/ diff --git a/.gitmodules b/.gitmodules index b825b907..14fc5342 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "Libs/mini-v3-ioc"] path = Libs/mini-v3-ioc url = https://github.com/RaccoonLabHardware/mini-v3-ioc.git +[submodule "Libs/Canopen"] + path = Libs/Canopen + url = git@github.com:Ilyhadev/Canopen.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 33b1a435..4158868f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ set(CMAKE_DIR ${ROOT_DIR}/cmake) set(LIBPARAMS_PATH ${ROOT_DIR}/Libs/libparams) set(LIBPARAMS_CMAKE libparams.cmake) set(NC_LIBDCNODE_PATH "${ROOT_DIR}/Libs/Dronecan" CACHE PATH "Path to libdcnode/DroneCAN source tree") +set(NC_LIBCANOPEN_PATH "${ROOT_DIR}/Libs/Canopen" CACHE PATH "Path to libcanopen source tree") set(BUILD_ROOT_DIR ${ROOT_DIR}/build) include(${CMAKE_DIR}/module.cmake) @@ -174,8 +175,14 @@ elseif(BOARD_PLATFORM STREQUAL "stm32f103") if(DEFINED STM32_CUBEMX_PROJECT_PATH) set(stm32cubeMxProjectPath ${STM32_CUBEMX_PROJECT_PATH}) else() - set(stm32cubeMxProjectPath ${ROOT_DIR}/Libs/stm32-cube-project) + message(FATAL_ERROR "STM32_CUBEMX_PROJECT_PATH is required for '${NC_BOARD}' (${BOARD_PLATFORM}).") endif() + if(NOT EXISTS "${stm32cubeMxProjectPath}") + message(FATAL_ERROR + "STM32_CUBEMX_PROJECT_PATH '${stm32cubeMxProjectPath}' does not exist. " + "Provide an STM32H753xx CubeMX project with -DSTM32_CUBEMX_PROJECT_PATH=.") + endif() + if(DEFINED STM32_LINKER_SCRIPT_PATH) set(ldFile ${STM32_LINKER_SCRIPT_PATH}) elseif(DEFINED STM32_LINKER_SCRIPT_TEMPLATE_PATH) @@ -213,8 +220,15 @@ elseif(BOARD_PLATFORM STREQUAL "stm32g0b1") if(DEFINED STM32_CUBEMX_PROJECT_PATH) set(stm32cubeMxProjectPath ${STM32_CUBEMX_PROJECT_PATH}) else() - set(stm32cubeMxProjectPath ${ROOT_DIR}/Libs/mini-v3-ioc) + message(FATAL_ERROR "STM32_CUBEMX_PROJECT_PATH is required for '${NC_BOARD}' (${BOARD_PLATFORM}).") endif() + + if(NOT EXISTS "${stm32cubeMxProjectPath}") + message(FATAL_ERROR + "STM32_CUBEMX_PROJECT_PATH '${stm32cubeMxProjectPath}' does not exist. " + "Provide an STM32H753xx CubeMX project with -DSTM32_CUBEMX_PROJECT_PATH=.") + endif() + if(DEFINED STM32_LINKER_SCRIPT_PATH) set(ldFile ${STM32_LINKER_SCRIPT_PATH}) elseif(DEFINED STM32_LINKER_SCRIPT_TEMPLATE_PATH) @@ -230,6 +244,19 @@ elseif(BOARD_PLATFORM STREQUAL "stm32g0b1") FILE(GLOB coreSources ${stm32cubeMxProjectPath}/Core/Src/*) FILE(GLOB driversSources ${stm32cubeMxProjectPath}/Drivers/*/*/*.c ${stm32cubeMxProjectPath}/Drivers/*/*/*/*.c) + set(cmsisDspPath ${stm32cubeMxProjectPath}/Middlewares/Third_Party/ARM_CMSIS/CMSIS/DSP) + set(cmsisDspSources + ${cmsisDspPath}/Source/BasicMathFunctions/arm_mult_q15.c + ${cmsisDspPath}/Source/BasicMathFunctions/arm_shift_q15.c + ${cmsisDspPath}/Source/CommonTables/arm_common_tables.c + ${cmsisDspPath}/Source/CommonTables/arm_const_structs.c + ${cmsisDspPath}/Source/SupportFunctions/arm_float_to_q15.c + ${cmsisDspPath}/Source/SupportFunctions/arm_q15_to_float.c + ${cmsisDspPath}/Source/TransformFunctions/arm_bitreversal2.c + ${cmsisDspPath}/Source/TransformFunctions/arm_cfft_q15.c + ${cmsisDspPath}/Source/TransformFunctions/arm_cfft_radix4_q15.c + ${cmsisDspPath}/Source/TransformFunctions/arm_rfft_init_q15.c + ${cmsisDspPath}/Source/TransformFunctions/arm_rfft_q15.c) FILE(GLOB_RECURSE middlewareSources ${stm32cubeMxProjectPath}/Middlewares/ST/STM32_USB_Device_Library/*.c) FILE(GLOB_RECURSE usbDeviceSources @@ -240,7 +267,6 @@ elseif(BOARD_PLATFORM STREQUAL "stm32g0b1") set(CMAKE_EXE_LINKER_FLAGS "-T${ldFile} -Wl,-Map=${PROJECT_NAME}.map,--cref") include(${CMAKE_DIR}/toolchain-stm32g0b1.cmake) - add_compile_definitions(NUM_OF_CAN_BUSES=2) add_compile_definitions(CYPHAL_NUM_OF_CAN_BUSES=2) elseif(BOARD_PLATFORM STREQUAL "stm32h753xx") if(LIBPARAMS_STORAGE_BACKEND STREQUAL "") @@ -303,9 +329,7 @@ elseif(BOARD_PLATFORM STREQUAL "stm32h753xx") set(CMAKE_EXE_LINKER_FLAGS "-T${ldFile} -Wl,-Map=${PROJECT_NAME}.map,--cref") include(${CMAKE_DIR}/toolchain-stm32h753xx.cmake) - add_compile_definitions(NUM_OF_CAN_BUSES=1) add_compile_definitions(CYPHAL_NUM_OF_CAN_BUSES=1) - add_compile_definitions(DRONECAN_FDCAN_PRIMARY=1) add_compile_definitions(USE_PWR_LDO_SUPPLY) else() message(FATAL_ERROR "Unknown BOARD_PLATFORM '${BOARD_PLATFORM}'. Expected: ubuntu, stm32f103, stm32g0b1, stm32h753xx.") diff --git a/Libs/Canopen b/Libs/Canopen new file mode 160000 index 00000000..47bffaf6 --- /dev/null +++ b/Libs/Canopen @@ -0,0 +1 @@ +Subproject commit 47bffaf6a3128c68db045966d200550d012671d6 diff --git a/Libs/Dronecan b/Libs/Dronecan index 6be0f6c4..f2d61f77 160000 --- a/Libs/Dronecan +++ b/Libs/Dronecan @@ -1 +1 @@ -Subproject commit 6be0f6c482e134002ff66454ab9f1733f2f7e00f +Subproject commit f2d61f77d94397158ce60effa4a7abb492c915b3 diff --git a/Libs/mini-v3-ioc b/Libs/mini-v3-ioc deleted file mode 160000 index 818360d3..00000000 --- a/Libs/mini-v3-ioc +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 818360d3986feaa84b458eef415f6d9fe7941e3d diff --git a/Libs/stm32-cube-project b/Libs/stm32-cube-project deleted file mode 160000 index 2e5cd947..00000000 --- a/Libs/stm32-cube-project +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2e5cd94758fbe0c24b7ac07426dc575611187e47 diff --git a/Makefile b/Makefile index 52b5c7e9..012af474 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,12 @@ ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) NC_BUILD_DIR?=$(ROOT_DIR)/build +MINI_V2_CUBEMX_MANIFEST:=$(ROOT_DIR)/Src/boards/rl/mini_v2/cubemx.json +MINI_V2_HAL:=$(NC_BUILD_DIR)/stm32cubemx/rl/mini_v2 + +MINI_V3_CUBEMX_MANIFEST:=$(ROOT_DIR)/Src/boards/rl/mini_v3/cubemx.json +MINI_V3_HAL:=$(NC_BUILD_DIR)/stm32cubemx/rl/mini_v3 + NODE_V4_CUBEMX_MANIFEST:=$(ROOT_DIR)/Src/boards/rl/node_v4/cubemx.json NODE_V4_HAL:=$(NC_BUILD_DIR)/stm32cubemx/rl/node_v4 @@ -76,6 +82,17 @@ endif --cmake-build-type "${CMAKE_BUILD_TYPE}" \ -- ${EXTRA_CMAKE_ARGS} +ensure_mini_v2_cubemx: + @python3 "$(ROOT_DIR)/scripts/cubemx.py" ensure \ + --manifest "$(MINI_V2_CUBEMX_MANIFEST)" \ + --out "$(MINI_V2_HAL)" + +ensure_mini_v3_cubemx: + @python3 "$(ROOT_DIR)/scripts/cubemx.py" ensure \ + --manifest "$(MINI_V3_CUBEMX_MANIFEST)" \ + --out "$(MINI_V3_HAL)" \ + --release-only + ensure_node_v4_cubemx: @python3 "$(ROOT_DIR)/scripts/cubemx.py" ensure \ --manifest "$(NODE_V4_CUBEMX_MANIFEST)" \ @@ -86,6 +103,14 @@ cubemx_archives: --manifest "$(NODE_V4_CUBEMX_MANIFEST)" \ --out "$(NODE_V4_HAL)" \ --archive-dir "$(ROOT_DIR)/release/cubemx" + @python3 "$(ROOT_DIR)/scripts/cubemx.py" package \ + --manifest "$(MINI_V3_CUBEMX_MANIFEST)" \ + --out "$(MINI_V3_HAL)" \ + --archive-dir "$(ROOT_DIR)/release/cubemx" + @python3 "$(ROOT_DIR)/scripts/cubemx.py" package \ + --manifest "$(MINI_V2_CUBEMX_MANIFEST)" \ + --out "$(MINI_V2_HAL)" \ + --archive-dir "$(ROOT_DIR)/release/cubemx" # # Alias naming rules: @@ -96,44 +121,62 @@ cubemx_archives: # rl/mini_v2 aliases # rl_mini_v2_dronecan: checks + $(MAKE) ensure_mini_v2_cubemx $(MAKE) build BOARD=rl/mini_v2 TARGET=dronecan IMAGE_KIND=application BUILD_VARIANT=rl_mini_v2_dronecan_application rl_mini_v2_dronecan_application: checks + $(MAKE) ensure_mini_v2_cubemx $(MAKE) build BOARD=rl/mini_v2 TARGET=dronecan IMAGE_KIND=application BUILD_VARIANT=rl_mini_v2_dronecan_application rl_mini_v2_dronecan_standalone: checks + $(MAKE) ensure_mini_v2_cubemx $(MAKE) build BOARD=rl/mini_v2 TARGET=dronecan IMAGE_KIND=standalone BUILD_VARIANT=rl_mini_v2_dronecan_standalone rl_mini_v2_cyphal: checks + $(MAKE) ensure_mini_v2_cubemx $(MAKE) build BOARD=rl/mini_v2 TARGET=cyphal IMAGE_KIND=application BUILD_VARIANT=rl_mini_v2_cyphal_application rl_mini_v2_cyphal_application: checks + $(MAKE) ensure_mini_v2_cubemx $(MAKE) build BOARD=rl/mini_v2 TARGET=cyphal IMAGE_KIND=application BUILD_VARIANT=rl_mini_v2_cyphal_application rl_mini_v2_cyphal_standalone: checks + $(MAKE) ensure_mini_v2_cubemx $(MAKE) build BOARD=rl/mini_v2 TARGET=cyphal IMAGE_KIND=standalone BUILD_VARIANT=rl_mini_v2_cyphal_standalone rl_mini_v2_both: checks + $(MAKE) ensure_mini_v2_cubemx $(MAKE) build BOARD=rl/mini_v2 TARGET=both IMAGE_KIND=application BUILD_VARIANT=rl_mini_v2_both_application rl_mini_v2_both_application: checks + $(MAKE) ensure_mini_v2_cubemx $(MAKE) build BOARD=rl/mini_v2 TARGET=both IMAGE_KIND=application BUILD_VARIANT=rl_mini_v2_both_application rl_mini_v2_both_standalone: checks + $(MAKE) ensure_mini_v2_cubemx $(MAKE) build BOARD=rl/mini_v2 TARGET=both IMAGE_KIND=standalone BUILD_VARIANT=rl_mini_v2_both_standalone # # rl/mini_v3 aliases # rl_mini_v3_dronecan: checks + $(MAKE) ensure_mini_v3_cubemx $(MAKE) build BOARD=rl/mini_v3 TARGET=dronecan IMAGE_KIND=application BUILD_VARIANT=rl_mini_v3_dronecan_application rl_mini_v3_dronecan_application: checks + $(MAKE) ensure_mini_v3_cubemx $(MAKE) build BOARD=rl/mini_v3 TARGET=dronecan IMAGE_KIND=application BUILD_VARIANT=rl_mini_v3_dronecan_application rl_mini_v3_dronecan_standalone: checks + $(MAKE) ensure_mini_v3_cubemx $(MAKE) build BOARD=rl/mini_v3 TARGET=dronecan IMAGE_KIND=standalone BUILD_VARIANT=rl_mini_v3_dronecan_standalone rl_mini_v3_cyphal: checks + $(MAKE) ensure_mini_v3_cubemx $(MAKE) build BOARD=rl/mini_v3 TARGET=cyphal IMAGE_KIND=application BUILD_VARIANT=rl_mini_v3_cyphal_application rl_mini_v3_cyphal_application: checks + $(MAKE) ensure_mini_v3_cubemx $(MAKE) build BOARD=rl/mini_v3 TARGET=cyphal IMAGE_KIND=application BUILD_VARIANT=rl_mini_v3_cyphal_application rl_mini_v3_cyphal_standalone: checks + $(MAKE) ensure_mini_v3_cubemx $(MAKE) build BOARD=rl/mini_v3 TARGET=cyphal IMAGE_KIND=standalone BUILD_VARIANT=rl_mini_v3_cyphal_standalone rl_mini_v3_both: checks + $(MAKE) ensure_mini_v3_cubemx $(MAKE) build BOARD=rl/mini_v3 TARGET=both IMAGE_KIND=application BUILD_VARIANT=rl_mini_v3_both_application rl_mini_v3_both_application: checks + $(MAKE) ensure_mini_v3_cubemx $(MAKE) build BOARD=rl/mini_v3 TARGET=both IMAGE_KIND=application BUILD_VARIANT=rl_mini_v3_both_application rl_mini_v3_both_standalone: checks + $(MAKE) ensure_mini_v3_cubemx $(MAKE) build BOARD=rl/mini_v3 TARGET=both IMAGE_KIND=standalone BUILD_VARIANT=rl_mini_v3_both_standalone # @@ -202,7 +245,7 @@ tests: ${ROOT_DIR}/scripts/tests.sh upload: - LATEST_TARGET=$$(ls -td ${NC_BUILD_DIR}/release/*.bin | head -1) && ./scripts/flash.sh $$LATEST_TARGET + LATEST_TARGET=$$(ls -td ${ROOT_DIR}/release/*.bin | head -1) && ./scripts/flash.sh $$LATEST_TARGET SOCKETCAN_URL:=https://gist.githubusercontent.com/PonomarevDA/6ecc8fc340e4c50619c1e5dfcedc37b2/raw/2db6d1626a9ada543602ff0a52b48fecb94e6e07/socketcan.sh SOCKETCAN_EXECUTABLE:=build/tools/socketcan-v1.0.2.sh @@ -220,7 +263,7 @@ remove_slcan0: download_socketcan run: create_slcan0 LATEST_TARGET=$$(ls -td build/*/obj/node | head -1 | head -1) && $$LATEST_TARGET clean_releases: - -rm -fR ${NC_BUILD_DIR}/release + -rm -f ${ROOT_DIR}/release/*.bin clean: -rm -fR ${NC_BUILD_DIR}/*/obj distclean: diff --git a/README.md b/README.md index d4fc2a69..5cb6fa25 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,9 @@ make rl_mini_v2_cyphal make rl_mini_v3_cyphal ``` + +> **Mini v3 IMU/DSP:** The IMU vibration analysis uses CMSIS-DSP, which is declared in the Mini v3 `.ioc`. Mini v3 builds intentionally restore the matching generated HAL from a GitHub release archive instead of regenerating it locally, ensuring that the complete DSP sources are available. After editing the Mini v3 `.ioc`, generate and publish its new HAL archive before building. See [docs/cubemx.md](docs/cubemx.md). + Note: Cyphal builds require access to the private `libcpnode` repository. Fork CI skips Cyphal (and combined v3) builds. See [docs/build_system.md](docs/build_system.md) for how the Make targets and the underlying CMake build relate. diff --git a/Src/boards/rl/mini_v2/board/board.cmake b/Src/boards/rl/mini_v2/board/board.cmake index b0e67d2e..79260354 100644 --- a/Src/boards/rl/mini_v2/board/board.cmake +++ b/Src/boards/rl/mini_v2/board/board.cmake @@ -2,7 +2,7 @@ # Distributed under the terms of the GPL v3 license, available in the file LICENSE. set(BOARD_PLATFORM stm32f103) -set(STM32_CUBEMX_PROJECT_PATH ${ROOT_DIR}/Libs/stm32-cube-project) +set(STM32_CUBEMX_PROJECT_PATH ${ROOT_DIR}/build/stm32cubemx/rl/mini_v2) if(NOT DEFINED IMAGE_KIND OR IMAGE_KIND STREQUAL "") set(IMAGE_KIND standalone) diff --git a/Src/boards/rl/mini_v2/board/project.ioc b/Src/boards/rl/mini_v2/board/project.ioc new file mode 100644 index 00000000..3cd5f5a4 --- /dev/null +++ b/Src/boards/rl/mini_v2/board/project.ioc @@ -0,0 +1,260 @@ +#MicroXplorer Configuration settings - do not modify +ADC1.Channel-0\#ChannelRegularConversion=ADC_CHANNEL_0 +ADC1.Channel-1\#ChannelRegularConversion=ADC_CHANNEL_1 +ADC1.Channel-2\#ChannelRegularConversion=ADC_CHANNEL_3 +ADC1.Channel-3\#ChannelRegularConversion=ADC_CHANNEL_9 +ADC1.Channel-4\#ChannelRegularConversion=ADC_CHANNEL_TEMPSENSOR +ADC1.IPParameters=Rank-0\#ChannelRegularConversion,Channel-0\#ChannelRegularConversion,SamplingTime-0\#ChannelRegularConversion,NbrOfConversionFlag,Rank-1\#ChannelRegularConversion,Channel-1\#ChannelRegularConversion,SamplingTime-1\#ChannelRegularConversion,Rank-2\#ChannelRegularConversion,Channel-2\#ChannelRegularConversion,SamplingTime-2\#ChannelRegularConversion,Rank-3\#ChannelRegularConversion,Channel-3\#ChannelRegularConversion,SamplingTime-3\#ChannelRegularConversion,NbrOfConversion,Rank-4\#ChannelRegularConversion,Channel-4\#ChannelRegularConversion,SamplingTime-4\#ChannelRegularConversion,master +ADC1.NbrOfConversion=5 +ADC1.NbrOfConversionFlag=1 +ADC1.Rank-0\#ChannelRegularConversion=1 +ADC1.Rank-1\#ChannelRegularConversion=2 +ADC1.Rank-2\#ChannelRegularConversion=3 +ADC1.Rank-3\#ChannelRegularConversion=4 +ADC1.Rank-4\#ChannelRegularConversion=5 +ADC1.SamplingTime-0\#ChannelRegularConversion=ADC_SAMPLETIME_55CYCLES_5 +ADC1.SamplingTime-1\#ChannelRegularConversion=ADC_SAMPLETIME_55CYCLES_5 +ADC1.SamplingTime-2\#ChannelRegularConversion=ADC_SAMPLETIME_55CYCLES_5 +ADC1.SamplingTime-3\#ChannelRegularConversion=ADC_SAMPLETIME_55CYCLES_5 +ADC1.SamplingTime-4\#ChannelRegularConversion=ADC_SAMPLETIME_55CYCLES_5 +ADC1.master=1 +CAD.formats= +CAD.pinconfig= +CAD.provider= +CAN.CalculateBaudRate=749999 +CAN.CalculateTimeBit=1333 +CAN.CalculateTimeQuantum=444.44444444444446 +CAN.IPParameters=CalculateTimeQuantum,CalculateTimeBit,CalculateBaudRate +Dma.ADC1.0.Direction=DMA_PERIPH_TO_MEMORY +Dma.ADC1.0.Instance=DMA1_Channel1 +Dma.ADC1.0.MemDataAlignment=DMA_MDATAALIGN_HALFWORD +Dma.ADC1.0.MemInc=DMA_MINC_ENABLE +Dma.ADC1.0.Mode=DMA_CIRCULAR +Dma.ADC1.0.PeriphDataAlignment=DMA_PDATAALIGN_HALFWORD +Dma.ADC1.0.PeriphInc=DMA_PINC_DISABLE +Dma.ADC1.0.Priority=DMA_PRIORITY_LOW +Dma.ADC1.0.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority +Dma.Request0=ADC1 +Dma.RequestsNb=1 +File.Version=6 +GPIO.groupedBy=Group By Peripherals +IWDG.IPParameters=Prescaler +IWDG.Prescaler=IWDG_PRESCALER_16 +KeepUserPlacement=false +Mcu.CPN=STM32F103T8U6 +Mcu.Family=STM32F1 +Mcu.IP0=ADC1 +Mcu.IP1=CAN +Mcu.IP10=USART2 +Mcu.IP2=DMA +Mcu.IP3=IWDG +Mcu.IP4=NVIC +Mcu.IP5=RCC +Mcu.IP6=SYS +Mcu.IP7=TIM3 +Mcu.IP8=TIM4 +Mcu.IP9=USART1 +Mcu.IPNb=11 +Mcu.Name=STM32F103T(8-B)Ux +Mcu.Package=VFQFPN36 +Mcu.Pin0=PD0-OSC_IN +Mcu.Pin1=PD1-OSC_OUT +Mcu.Pin10=PA10 +Mcu.Pin11=PA11 +Mcu.Pin12=PA12 +Mcu.Pin13=PA13 +Mcu.Pin14=PA14 +Mcu.Pin15=PA15 +Mcu.Pin16=PB4 +Mcu.Pin17=PB5 +Mcu.Pin18=PB6 +Mcu.Pin19=PB7 +Mcu.Pin2=PA0-WKUP +Mcu.Pin20=VP_ADC1_TempSens_Input +Mcu.Pin21=VP_IWDG_VS_IWDG +Mcu.Pin22=VP_SYS_VS_tim1 +Mcu.Pin23=VP_TIM3_VS_ClockSourceINT +Mcu.Pin3=PA1 +Mcu.Pin4=PA2 +Mcu.Pin5=PA3 +Mcu.Pin6=PA4 +Mcu.Pin7=PB1 +Mcu.Pin8=PA8 +Mcu.Pin9=PA9 +Mcu.PinsNb=24 +Mcu.ThirdPartyNb=0 +Mcu.UserConstants= +Mcu.UserName=STM32F103T8Ux +MxCube.Version=6.10.0 +MxDb.Version=DB.6.0.100 +NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.DMA1_Channel1_IRQn=true\:0\:0\:false\:false\:true\:false\:true\:true +NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.ForceEnableDMAVector=true +NVIC.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.PendSV_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4 +NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.SysTick_IRQn=true\:15\:0\:false\:false\:true\:false\:true\:false +NVIC.TIM1_UP_IRQn=true\:15\:0\:false\:false\:true\:false\:true\:true +NVIC.TimeBase=TIM1_UP_IRQn +NVIC.TimeBaseIP=TIM1 +NVIC.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +PA0-WKUP.GPIOParameters=GPIO_Label +PA0-WKUP.GPIO_Label=ADC_VIN +PA0-WKUP.Locked=true +PA0-WKUP.Signal=ADCx_IN0 +PA1.GPIOParameters=GPIO_Label +PA1.GPIO_Label=ADC_5V +PA1.Locked=true +PA1.Signal=ADCx_IN1 +PA10.Mode=Asynchronous +PA10.Signal=USART1_RX +PA11.Mode=CAN_Activate +PA11.Signal=CAN_RX +PA12.Mode=CAN_Activate +PA12.Signal=CAN_TX +PA13.Mode=Serial_Wire +PA13.Signal=SYS_JTMS-SWDIO +PA14.Mode=Serial_Wire +PA14.Signal=SYS_JTCK-SWCLK +PA15.GPIOParameters=GPIO_Label +PA15.GPIO_Label=INT_RGB_LED_RED +PA15.Locked=true +PA15.Signal=GPIO_Output +PA2.Locked=true +PA2.Mode=Half_duplex(single_wire_mode) +PA2.Signal=USART2_TX +PA3.GPIOParameters=GPIO_Label +PA3.GPIO_Label=ADC_CURRENT +PA3.Locked=true +PA3.Signal=ADCx_IN3 +PA4.GPIOParameters=GPIO_Label +PA4.GPIO_Label=INT_RGB_LED_BLUE +PA4.Locked=true +PA4.Signal=GPIO_Output +PA8.GPIOParameters=GPIO_Label +PA8.GPIO_Label=INT_RGB_LED_GREEN +PA8.Locked=true +PA8.Signal=GPIO_Output +PA9.Mode=Asynchronous +PA9.Signal=USART1_TX +PB1.GPIOParameters=GPIO_Label +PB1.GPIO_Label=ADC_VERSION +PB1.Locked=true +PB1.Signal=ADCx_IN9 +PB4.GPIOParameters=GPIO_Label +PB4.GPIO_Label=PWM3_TIM3_CH1 +PB4.Locked=true +PB4.Signal=S_TIM3_CH1 +PB5.GPIOParameters=GPIO_Label +PB5.GPIO_Label=PWM4_TIM3_CH2 +PB5.Locked=true +PB5.Signal=S_TIM3_CH2 +PB6.GPIOParameters=GPIO_Label +PB6.GPIO_Label=PWM2_TIM4_CH1 +PB6.Signal=S_TIM4_CH1 +PB7.GPIOParameters=GPIO_Label +PB7.GPIO_Label=PWM1_TIM4_CH2 +PB7.Signal=S_TIM4_CH2 +PD0-OSC_IN.Mode=HSE-External-Oscillator +PD0-OSC_IN.Signal=RCC_OSC_IN +PD1-OSC_OUT.Mode=HSE-External-Oscillator +PD1-OSC_OUT.Signal=RCC_OSC_OUT +PinOutPanel.RotationAngle=0 +ProjectManager.AskForMigrate=true +ProjectManager.BackupPrevious=false +ProjectManager.CompilerOptimize=6 +ProjectManager.ComputerToolchain=false +ProjectManager.CoupleFile=true +ProjectManager.CustomerFirmwarePackage= +ProjectManager.DefaultFWLocation=true +ProjectManager.DeletePrevious=true +ProjectManager.DeviceId=STM32F103T8Ux +ProjectManager.FirmwarePackage=STM32Cube FW_F1 V1.8.4 +ProjectManager.FreePins=false +ProjectManager.HalAssertFull=false +ProjectManager.HeapSize=0x200 +ProjectManager.KeepUserCode=true +ProjectManager.LastFirmware=false +ProjectManager.LibraryCopy=1 +ProjectManager.MainLocation=Core/Src +ProjectManager.NoMain=false +ProjectManager.PreviousToolchain=STM32CubeIDE +ProjectManager.ProjectBuild=false +ProjectManager.ProjectFileName=project_v2.ioc +ProjectManager.ProjectName=project_v2 +ProjectManager.ProjectStructure= +ProjectManager.RegisterCallBack= +ProjectManager.StackSize=0x400 +ProjectManager.TargetToolchain=STM32CubeIDE +ProjectManager.ToolChainLocation= +ProjectManager.UAScriptAfterPath= +ProjectManager.UAScriptBeforePath= +ProjectManager.UnderRoot=true +ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_DMA_Init-DMA-false-HAL-true,4-MX_CAN_Init-CAN-false-HAL-true,5-MX_TIM4_Init-TIM4-false-HAL-true,6-MX_TIM3_Init-TIM3-false-HAL-true,7-MX_ADC1_Init-ADC1-false-HAL-true,8-MX_IWDG_Init-IWDG-false-HAL-true,9-MX_USART1_UART_Init-USART1-false-HAL-true,10-MX_USART2_UART_Init-USART2-false-HAL-true,11-MX_ADC2_Init-ADC2-false-HAL-true +RCC.ADCFreqValue=12000000 +RCC.ADCPresc=RCC_ADCPCLK2_DIV6 +RCC.AHBFreq_Value=72000000 +RCC.APB1CLKDivider=RCC_HCLK_DIV2 +RCC.APB1Freq_Value=36000000 +RCC.APB1TimFreq_Value=72000000 +RCC.APB2Freq_Value=72000000 +RCC.APB2TimFreq_Value=72000000 +RCC.FCLKCortexFreq_Value=72000000 +RCC.FamilyName=M +RCC.HCLKFreq_Value=72000000 +RCC.IPParameters=ADCFreqValue,ADCPresc,AHBFreq_Value,APB1CLKDivider,APB1Freq_Value,APB1TimFreq_Value,APB2Freq_Value,APB2TimFreq_Value,FCLKCortexFreq_Value,FamilyName,HCLKFreq_Value,MCOFreq_Value,PLLCLKFreq_Value,PLLMCOFreq_Value,PLLMUL,PLLSourceVirtual,SYSCLKFreq_VALUE,SYSCLKSource,TimSysFreq_Value,USBFreq_Value,VCOOutput2Freq_Value +RCC.MCOFreq_Value=72000000 +RCC.PLLCLKFreq_Value=72000000 +RCC.PLLMCOFreq_Value=36000000 +RCC.PLLMUL=RCC_PLL_MUL9 +RCC.PLLSourceVirtual=RCC_PLLSOURCE_HSE +RCC.SYSCLKFreq_VALUE=72000000 +RCC.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK +RCC.TimSysFreq_Value=72000000 +RCC.USBFreq_Value=72000000 +RCC.VCOOutput2Freq_Value=8000000 +SH.ADCx_IN0.0=ADC1_IN0,IN0 +SH.ADCx_IN0.ConfNb=1 +SH.ADCx_IN1.0=ADC1_IN1,IN1 +SH.ADCx_IN1.ConfNb=1 +SH.ADCx_IN3.0=ADC1_IN3,IN3 +SH.ADCx_IN3.ConfNb=1 +SH.ADCx_IN9.0=ADC1_IN9,IN9 +SH.ADCx_IN9.ConfNb=1 +SH.S_TIM3_CH1.0=TIM3_CH1,PWM Generation1 CH1 +SH.S_TIM3_CH1.ConfNb=1 +SH.S_TIM3_CH2.0=TIM3_CH2,PWM Generation2 CH2 +SH.S_TIM3_CH2.ConfNb=1 +SH.S_TIM4_CH1.0=TIM4_CH1,PWM Generation1 CH1 +SH.S_TIM4_CH1.ConfNb=1 +SH.S_TIM4_CH2.0=TIM4_CH2,PWM Generation2 CH2 +SH.S_TIM4_CH2.ConfNb=1 +TIM3.Channel-PWM\ Generation1\ CH1=TIM_CHANNEL_1 +TIM3.Channel-PWM\ Generation2\ CH2=TIM_CHANNEL_2 +TIM3.IPParameters=Channel-PWM Generation1 CH1,Channel-PWM Generation2 CH2,Period,Prescaler +TIM3.Period=20000 +TIM3.Prescaler=71 +TIM4.Channel-PWM\ Generation1\ CH1=TIM_CHANNEL_1 +TIM4.Channel-PWM\ Generation2\ CH2=TIM_CHANNEL_2 +TIM4.IPParameters=Channel-PWM Generation2 CH2,Channel-PWM Generation1 CH1,Period,Prescaler +TIM4.Period=20000 +TIM4.Prescaler=71 +USART1.IPParameters=VirtualMode,Mode +USART1.Mode=MODE_TX_RX +USART1.VirtualMode=VM_ASYNC +USART2.IPParameters=VirtualMode,Mode +USART2.Mode=MODE_RX +USART2.VirtualMode=VM_ASYNC +VP_ADC1_TempSens_Input.Mode=IN-TempSens +VP_ADC1_TempSens_Input.Signal=ADC1_TempSens_Input +VP_IWDG_VS_IWDG.Mode=IWDG_Activate +VP_IWDG_VS_IWDG.Signal=IWDG_VS_IWDG +VP_SYS_VS_tim1.Mode=TIM1 +VP_SYS_VS_tim1.Signal=SYS_VS_tim1 +VP_TIM3_VS_ClockSourceINT.Mode=Internal +VP_TIM3_VS_ClockSourceINT.Signal=TIM3_VS_ClockSourceINT +board=custom diff --git a/Src/boards/rl/mini_v2/cubemx.json b/Src/boards/rl/mini_v2/cubemx.json new file mode 100644 index 00000000..f5db9433 --- /dev/null +++ b/Src/boards/rl/mini_v2/cubemx.json @@ -0,0 +1,8 @@ +{ + "name": "rl-mini-v2", + "ioc": "board/project.ioc", + "store": { + "repo": "RaccoonlabDev/mini_v2_node", + "tag": "cubemx-v0.1.0" + } +} diff --git a/Src/boards/rl/mini_v3/board/board.cmake b/Src/boards/rl/mini_v3/board/board.cmake index 027d32c9..2511d4da 100644 --- a/Src/boards/rl/mini_v3/board/board.cmake +++ b/Src/boards/rl/mini_v3/board/board.cmake @@ -2,7 +2,7 @@ # Distributed under the terms of the GPL v3 license, available in the file LICENSE. set(BOARD_PLATFORM stm32g0b1) -set(STM32_CUBEMX_PROJECT_PATH ${ROOT_DIR}/Libs/mini-v3-ioc) +set(STM32_CUBEMX_PROJECT_PATH ${ROOT_DIR}/build/stm32cubemx/rl/mini_v3) if(NOT DEFINED IMAGE_KIND OR IMAGE_KIND STREQUAL "") set(IMAGE_KIND standalone) diff --git a/Src/boards/rl/mini_v3/board/project.ioc b/Src/boards/rl/mini_v3/board/project.ioc new file mode 100644 index 00000000..e0563f19 --- /dev/null +++ b/Src/boards/rl/mini_v3/board/project.ioc @@ -0,0 +1,336 @@ +#MicroXplorer Configuration settings - do not modify +ADC1.Channel-0\#ChannelRegularConversion=ADC_CHANNEL_0 +ADC1.Channel-1\#ChannelRegularConversion=ADC_CHANNEL_1 +ADC1.Channel-2\#ChannelRegularConversion=ADC_CHANNEL_6 +ADC1.Channel-3\#ChannelRegularConversion=ADC_CHANNEL_7 +ADC1.Channel-4\#ChannelRegularConversion=ADC_CHANNEL_TEMPSENSOR +ADC1.IPParameters=Rank-0\#ChannelRegularConversion,Channel-0\#ChannelRegularConversion,SamplingTime-0\#ChannelRegularConversion,NbrOfConversionFlag,SamplingTimeCommon2,SamplingTimeCommon1,Rank-1\#ChannelRegularConversion,Channel-1\#ChannelRegularConversion,SamplingTime-1\#ChannelRegularConversion,Rank-2\#ChannelRegularConversion,Channel-2\#ChannelRegularConversion,SamplingTime-2\#ChannelRegularConversion,Rank-3\#ChannelRegularConversion,Channel-3\#ChannelRegularConversion,SamplingTime-3\#ChannelRegularConversion,NbrOfConversion,master,SelectedChannel,Rank-4\#ChannelRegularConversion,Channel-4\#ChannelRegularConversion,SamplingTime-4\#ChannelRegularConversion +ADC1.NbrOfConversion=5 +ADC1.NbrOfConversionFlag=1 +ADC1.Rank-0\#ChannelRegularConversion=1 +ADC1.Rank-1\#ChannelRegularConversion=2 +ADC1.Rank-2\#ChannelRegularConversion=3 +ADC1.Rank-3\#ChannelRegularConversion=4 +ADC1.Rank-4\#ChannelRegularConversion=5 +ADC1.SamplingTime-0\#ChannelRegularConversion=ADC_SAMPLINGTIME_COMMON_1 +ADC1.SamplingTime-1\#ChannelRegularConversion=ADC_SAMPLINGTIME_COMMON_1 +ADC1.SamplingTime-2\#ChannelRegularConversion=ADC_SAMPLINGTIME_COMMON_1 +ADC1.SamplingTime-3\#ChannelRegularConversion=ADC_SAMPLINGTIME_COMMON_1 +ADC1.SamplingTime-4\#ChannelRegularConversion=ADC_SAMPLINGTIME_COMMON_1 +ADC1.SamplingTimeCommon1=ADC_SAMPLETIME_79CYCLES_5 +ADC1.SamplingTimeCommon2=ADC_SAMPLETIME_79CYCLES_5 +ADC1.SelectedChannel=ADC_CHANNEL_0|ADC_CHANNEL_1|ADC_CHANNEL_6|ADC_CHANNEL_7|ADC_CHANNEL_TEMPSENSOR +ADC1.master=1 +ARM.CMSIS.5.9.0.CMSISJjCORE=true +ARM.CMSIS.5.9.0.CMSISJjCORE_Checked=true +ARM.CMSIS.5.9.0.CMSISJjDSP=Source +ARM.CMSIS.5.9.0.CMSISJjDSP_Checked=true +ARM.CMSIS.5.9.0.IPParameters=CMSISJjCORE,CMSISJjDSP +ARM.CMSIS.5.9.0_SwParameter=CMSISJjDSP\:Source;CMSISJjCORE\:true; +CAD.formats= +CAD.pinconfig= +CAD.provider= +Dma.ADC1.0.Direction=DMA_PERIPH_TO_MEMORY +Dma.ADC1.0.EventEnable=DISABLE +Dma.ADC1.0.Instance=DMA1_Channel1 +Dma.ADC1.0.MemDataAlignment=DMA_MDATAALIGN_HALFWORD +Dma.ADC1.0.MemInc=DMA_MINC_ENABLE +Dma.ADC1.0.Mode=DMA_CIRCULAR +Dma.ADC1.0.PeriphDataAlignment=DMA_PDATAALIGN_HALFWORD +Dma.ADC1.0.PeriphInc=DMA_PINC_DISABLE +Dma.ADC1.0.Polarity=HAL_DMAMUX_REQ_GEN_RISING +Dma.ADC1.0.Priority=DMA_PRIORITY_LOW +Dma.ADC1.0.RequestNumber=1 +Dma.ADC1.0.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,SignalID,Polarity,RequestNumber,SyncSignalID,SyncPolarity,SyncEnable,EventEnable,SyncRequestNumber +Dma.ADC1.0.SignalID=NONE +Dma.ADC1.0.SyncEnable=DISABLE +Dma.ADC1.0.SyncPolarity=HAL_DMAMUX_SYNC_NO_EVENT +Dma.ADC1.0.SyncRequestNumber=1 +Dma.ADC1.0.SyncSignalID=NONE +Dma.Request0=ADC1 +Dma.RequestsNb=1 +FDCAN1.AutoRetransmission=ENABLE +FDCAN1.CalculateBaudRateNominal=1000000 +FDCAN1.CalculateTimeBitNominal=1000 +FDCAN1.CalculateTimeQuantumNominal=125.0 +FDCAN1.ClockDivider=FDCAN_CLOCK_DIV1 +FDCAN1.DataPrescaler=1 +FDCAN1.DataTimeSeg1=1 +FDCAN1.DataTimeSeg2=1 +FDCAN1.FrameFormat=FDCAN_FRAME_CLASSIC +FDCAN1.IPParameters=CalculateTimeQuantumNominal,CalculateTimeBitNominal,CalculateBaudRateNominal,ClockDivider,NominalPrescaler,NominalTimeSeg1,DataTimeSeg2,NominalSyncJumpWidth,FrameFormat,DataTimeSeg1,DataPrescaler,TxFifoQueueMode,Mode,AutoRetransmission,TransmitPause +FDCAN1.Mode=FDCAN_MODE_NORMAL +FDCAN1.NominalPrescaler=4 +FDCAN1.NominalSyncJumpWidth=1 +FDCAN1.NominalTimeSeg1=5 +FDCAN1.TransmitPause=ENABLE +FDCAN1.TxFifoQueueMode=FDCAN_TX_FIFO_OPERATION +FDCAN2.AutoRetransmission=ENABLE +FDCAN2.CalculateBaudRateNominal=1000000 +FDCAN2.CalculateTimeBitNominal=1000 +FDCAN2.CalculateTimeQuantumNominal=125.0 +FDCAN2.IPParameters=CalculateTimeQuantumNominal,CalculateTimeBitNominal,CalculateBaudRateNominal,AutoRetransmission,TransmitPause,NominalPrescaler,NominalTimeSeg1 +FDCAN2.NominalPrescaler=4 +FDCAN2.NominalTimeSeg1=5 +FDCAN2.TransmitPause=ENABLE +File.Version=6 +IWDG.IPParameters=Prescaler +IWDG.Prescaler=IWDG_PRESCALER_4 +KeepUserPlacement=false +Mcu.CPN=STM32G0B1CEU6 +Mcu.Family=STM32G0 +Mcu.IP0=ADC1 +Mcu.IP1=DMA +Mcu.IP2=FDCAN1 +Mcu.IP3=FDCAN2 +Mcu.IP4=IWDG +Mcu.IP5=NVIC +Mcu.IP6=RCC +Mcu.IP7=SPI2 +Mcu.IP8=SYS +Mcu.IP9=TIM4 +Mcu.IPNb=10 +Mcu.Name=STM32G0B1C(B-C-E)Ux +Mcu.Package=UFQFPN48 +Mcu.Pin0=PC13 +Mcu.Pin1=PC14-OSC32_IN (PC14) +Mcu.Pin10=PB1 +Mcu.Pin11=PB11 +Mcu.Pin12=PB12 +Mcu.Pin13=PB13 +Mcu.Pin14=PB14 +Mcu.Pin15=PB15 +Mcu.Pin16=PA13 +Mcu.Pin17=PA14-BOOT0 +Mcu.Pin18=PA15 +Mcu.Pin19=PD0 +Mcu.Pin2=PC15-OSC32_OUT (PC15) +Mcu.Pin20=PD1 +Mcu.Pin21=PB6 +Mcu.Pin22=PB7 +Mcu.Pin23=PB8 +Mcu.Pin24=PB9 +Mcu.Pin25=VP_ADC1_TempSens_Input +Mcu.Pin26=VP_IWDG_VS_IWDG +Mcu.Pin27=VP_SYS_VS_tim1 +Mcu.Pin28=VP_SYS_VS_DBSignals +Mcu.Pin29=VP_TIM4_VS_ClockSourceINT +Mcu.Pin3=PF0-OSC_IN (PF0) +Mcu.Pin30=VP_ARM.CMSIS_VS_CMSISJjCORE_5.6.0_5.9.0 +Mcu.Pin31=VP_ARM.CMSIS_VS_CMSISJjDSP_1.10.0_5.9.0 +Mcu.Pin4=PF1-OSC_OUT (PF1) +Mcu.Pin5=PA0 +Mcu.Pin6=PA1 +Mcu.Pin7=PA6 +Mcu.Pin8=PA7 +Mcu.Pin9=PB0 +Mcu.PinsNb=32 +Mcu.ThirdParty0=ARM.CMSIS.5.9.0 +Mcu.ThirdPartyNb=1 +Mcu.UserConstants= +Mcu.UserName=STM32G0B1CEUx +MxCube.Version=6.10.0 +MxDb.Version=DB.6.0.100 +NVIC.DMA1_Channel1_IRQn=true\:0\:0\:false\:false\:true\:false\:true\:true +NVIC.ForceEnableDMAVector=true +NVIC.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.PendSV_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.SVC_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:true +NVIC.SysTick_IRQn=true\:3\:0\:false\:false\:true\:false\:true\:false +NVIC.TIM16_FDCAN_IT0_IRQn=true\:0\:0\:false\:false\:true\:true\:true\:true +NVIC.TIM1_BRK_UP_TRG_COM_IRQn=true\:3\:0\:false\:false\:true\:false\:true\:true +NVIC.TimeBase=TIM1_BRK_UP_TRG_COM_IRQn +NVIC.TimeBaseIP=TIM1 +PA0.GPIOParameters=GPIO_Label +PA0.GPIO_Label=ADC_VIN +PA0.Locked=true +PA0.Mode=IN0 +PA0.Signal=ADC1_IN0 +PA1.GPIOParameters=GPIO_Label +PA1.GPIO_Label=ADC_5V +PA1.Locked=true +PA1.Mode=IN1 +PA1.Signal=ADC1_IN1 +PA13.Mode=Serial_Wire +PA13.Signal=SYS_SWDIO +PA14-BOOT0.Mode=Serial_Wire +PA14-BOOT0.Signal=SYS_SWCLK +PA15.GPIOParameters=GPIO_Label +PA15.GPIO_Label=CAN1_TERMINATOR +PA15.Locked=true +PA15.Signal=GPIO_Output +PA6.GPIOParameters=GPIO_Label +PA6.GPIO_Label=ADC_CURRENT +PA6.Locked=true +PA6.Mode=IN6 +PA6.Signal=ADC1_IN6 +PA7.GPIOParameters=GPIO_Label +PA7.GPIO_Label=ADC_VERSION +PA7.Locked=true +PA7.Mode=IN7 +PA7.Signal=ADC1_IN7 +PB0.Mode=FDCAN_Activate +PB0.Signal=FDCAN2_RX +PB1.Mode=FDCAN_Activate +PB1.Signal=FDCAN2_TX +PB11.Locked=true +PB11.Mode=Full_Duplex_Master +PB11.Signal=SPI2_MOSI +PB12.GPIOParameters=GPIO_Label +PB12.GPIO_Label=SPI2_NSS +PB12.Locked=true +PB12.Signal=GPIO_Output +PB13.Locked=true +PB13.Mode=Full_Duplex_Master +PB13.Signal=SPI2_SCK +PB14.Locked=true +PB14.Mode=Full_Duplex_Master +PB14.Signal=SPI2_MISO +PB15.GPIOParameters=GPIO_Label +PB15.GPIO_Label=CAN2_TERMINATOR +PB15.Locked=true +PB15.Signal=GPIO_Output +PB6.Locked=true +PB6.Signal=S_TIM4_CH1 +PB7.Locked=true +PB7.Signal=S_TIM4_CH2 +PB8.Locked=true +PB8.Signal=S_TIM4_CH3 +PB9.Locked=true +PB9.Signal=S_TIM4_CH4 +PC13.GPIOParameters=GPIO_Label +PC13.GPIO_Label=INTERNAL_LED_RED +PC13.Locked=true +PC13.Signal=GPIO_Output +PC14-OSC32_IN\ (PC14).GPIOParameters=GPIO_Label +PC14-OSC32_IN\ (PC14).GPIO_Label=INTERNAL_LED_GREEN +PC14-OSC32_IN\ (PC14).Locked=true +PC14-OSC32_IN\ (PC14).Signal=GPIO_Output +PC15-OSC32_OUT\ (PC15).GPIOParameters=GPIO_Label +PC15-OSC32_OUT\ (PC15).GPIO_Label=INTERNAL_LED_BLUE +PC15-OSC32_OUT\ (PC15).Locked=true +PC15-OSC32_OUT\ (PC15).Signal=GPIO_Output +PD0.Locked=true +PD0.Mode=FDCAN_Activate +PD0.Signal=FDCAN1_RX +PD1.Locked=true +PD1.Mode=FDCAN_Activate +PD1.Signal=FDCAN1_TX +PF0-OSC_IN\ (PF0).Mode=HSE-External-Oscillator +PF0-OSC_IN\ (PF0).Signal=RCC_OSC_IN +PF1-OSC_OUT\ (PF1).Mode=HSE-External-Oscillator +PF1-OSC_OUT\ (PF1).Signal=RCC_OSC_OUT +PinOutPanel.RotationAngle=0 +ProjectManager.AskForMigrate=true +ProjectManager.BackupPrevious=false +ProjectManager.CompilerLinker=GCC +ProjectManager.CompilerOptimize=6 +ProjectManager.ComputerToolchain=false +ProjectManager.CoupleFile=true +ProjectManager.CustomerFirmwarePackage= +ProjectManager.DefaultFWLocation=true +ProjectManager.DeletePrevious=true +ProjectManager.DeviceId=STM32G0B1CEUx +ProjectManager.FirmwarePackage=STM32Cube FW_G0 V1.6.1 +ProjectManager.FreePins=false +ProjectManager.FreePinsContext= +ProjectManager.HalAssertFull=false +ProjectManager.HeapSize=0x200 +ProjectManager.KeepUserCode=true +ProjectManager.LastFirmware=true +ProjectManager.LibraryCopy=1 +ProjectManager.MainLocation=Core/Src +ProjectManager.NoMain=false +ProjectManager.PreviousToolchain=STM32CubeIDE +ProjectManager.ProjectBuild=false +ProjectManager.ProjectFileName=project.ioc +ProjectManager.ProjectName=project +ProjectManager.ProjectStructure= +ProjectManager.RegisterCallBack= +ProjectManager.StackSize=0x400 +ProjectManager.TargetToolchain=STM32CubeIDE +ProjectManager.ToolChainLocation= +ProjectManager.UAScriptAfterPath= +ProjectManager.UAScriptBeforePath= +ProjectManager.UnderRoot=true +ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_DMA_Init-DMA-false-HAL-true,4-MX_FDCAN1_Init-FDCAN1-false-HAL-true,5-MX_ADC1_Init-ADC1-false-HAL-true,6-MX_FDCAN2_Init-FDCAN2-false-HAL-true,7-MX_TIM4_Init-TIM4-false-HAL-true,8-MX_SPI2_Init-SPI2-false-HAL-true,9-MX_IWDG_Init-IWDG-false-HAL-true +RCC.ADCFreq_Value=64000000 +RCC.AHBFreq_Value=64000000 +RCC.APB1CLKDivider=RCC_HCLK_DIV2 +RCC.APBFreq_Value=32000000 +RCC.APBTimFreq_Value=64000000 +RCC.CECFreq_Value=32786.88524590164 +RCC.CortexFreq_Value=64000000 +RCC.EXTERNAL_CLOCK_VALUE=48000 +RCC.FCLKCortexFreq_Value=64000000 +RCC.FDCANFreq_Value=32000000 +RCC.FamilyName=M +RCC.HCLKFreq_Value=64000000 +RCC.HSE_VALUE=8000000 +RCC.HSI48_VALUE=48000000 +RCC.HSI_VALUE=16000000 +RCC.I2C1Freq_Value=32000000 +RCC.I2C2Freq_Value=32000000 +RCC.I2S1Freq_Value=64000000 +RCC.I2S2Freq_Value=64000000 +RCC.IPParameters=ADCFreq_Value,AHBFreq_Value,APB1CLKDivider,APBFreq_Value,APBTimFreq_Value,CECFreq_Value,CortexFreq_Value,EXTERNAL_CLOCK_VALUE,FCLKCortexFreq_Value,FDCANFreq_Value,FamilyName,HCLKFreq_Value,HSE_VALUE,HSI48_VALUE,HSI_VALUE,I2C1Freq_Value,I2C2Freq_Value,I2S1Freq_Value,I2S2Freq_Value,LPTIM1Freq_Value,LPTIM2Freq_Value,LPUART1Freq_Value,LPUART2Freq_Value,LSCOPinFreq_Value,LSE_VALUE,LSI_VALUE,MCO1PinFreq_Value,MCO2PinFreq_Value,PLLPoutputFreq_Value,PLLQoutputFreq_Value,PLLRCLKFreq_Value,PWRFreq_Value,SYSCLKFreq_VALUE,SYSCLKSource,TIM15Freq_Value,TIM1Freq_Value,USART1Freq_Value,USART2Freq_Value,USART3Freq_Value,USBFreq_Value,VCOInputFreq_Value,VCOOutputFreq_Value +RCC.LPTIM1Freq_Value=32000000 +RCC.LPTIM2Freq_Value=32000000 +RCC.LPUART1Freq_Value=32000000 +RCC.LPUART2Freq_Value=32000000 +RCC.LSCOPinFreq_Value=32000 +RCC.LSE_VALUE=32768 +RCC.LSI_VALUE=32000 +RCC.MCO1PinFreq_Value=64000000 +RCC.MCO2PinFreq_Value=64000000 +RCC.PLLPoutputFreq_Value=64000000 +RCC.PLLQoutputFreq_Value=64000000 +RCC.PLLRCLKFreq_Value=64000000 +RCC.PWRFreq_Value=64000000 +RCC.SYSCLKFreq_VALUE=64000000 +RCC.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK +RCC.TIM15Freq_Value=64000000 +RCC.TIM1Freq_Value=64000000 +RCC.USART1Freq_Value=32000000 +RCC.USART2Freq_Value=32000000 +RCC.USART3Freq_Value=32000000 +RCC.USBFreq_Value=48000000 +RCC.VCOInputFreq_Value=16000000 +RCC.VCOOutputFreq_Value=128000000 +SH.S_TIM4_CH1.0=TIM4_CH1,PWM Generation1 CH1 +SH.S_TIM4_CH1.ConfNb=1 +SH.S_TIM4_CH2.0=TIM4_CH2,PWM Generation2 CH2 +SH.S_TIM4_CH2.ConfNb=1 +SH.S_TIM4_CH3.0=TIM4_CH3,Forced Output3 CH3 +SH.S_TIM4_CH3.ConfNb=1 +SH.S_TIM4_CH4.0=TIM4_CH4,PWM Generation4 CH4 +SH.S_TIM4_CH4.ConfNb=1 +SPI2.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_32 +SPI2.CalculateBaudRate=1000.0 KBits/s +SPI2.Direction=SPI_DIRECTION_2LINES +SPI2.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate,BaudRatePrescaler +SPI2.Mode=SPI_MODE_MASTER +SPI2.VirtualType=VM_MASTER +TIM4.Channel-Forced\ Output3\ CH3=TIM_CHANNEL_3 +TIM4.Channel-PWM\ Generation1\ CH1=TIM_CHANNEL_1 +TIM4.Channel-PWM\ Generation2\ CH2=TIM_CHANNEL_2 +TIM4.Channel-PWM\ Generation4\ CH4=TIM_CHANNEL_4 +TIM4.IPParameters=Channel-PWM Generation1 CH1,Channel-PWM Generation2 CH2,Channel-Forced Output3 CH3,Channel-PWM Generation4 CH4,Prescaler,Period +TIM4.Period=20000 +TIM4.Prescaler=63 +VP_ADC1_TempSens_Input.Mode=IN-TempSens +VP_ADC1_TempSens_Input.Signal=ADC1_TempSens_Input +VP_ARM.CMSIS_VS_CMSISJjCORE_5.6.0_5.9.0.Mode=CMSISJjCORE +VP_ARM.CMSIS_VS_CMSISJjCORE_5.6.0_5.9.0.Signal=ARM.CMSIS_VS_CMSISJjCORE_5.6.0_5.9.0 +VP_ARM.CMSIS_VS_CMSISJjDSP_1.10.0_5.9.0.Mode=CMSISJjDSP +VP_ARM.CMSIS_VS_CMSISJjDSP_1.10.0_5.9.0.Signal=ARM.CMSIS_VS_CMSISJjDSP_1.10.0_5.9.0 +VP_IWDG_VS_IWDG.Mode=IWDG_Activate +VP_IWDG_VS_IWDG.Signal=IWDG_VS_IWDG +VP_SYS_VS_DBSignals.Mode=DisableDeadBatterySignals +VP_SYS_VS_DBSignals.Signal=SYS_VS_DBSignals +VP_SYS_VS_tim1.Mode=TIM1 +VP_SYS_VS_tim1.Signal=SYS_VS_tim1 +VP_TIM4_VS_ClockSourceINT.Mode=Internal +VP_TIM4_VS_ClockSourceINT.Signal=TIM4_VS_ClockSourceINT +board=custom diff --git a/Src/boards/rl/mini_v3/cubemx.json b/Src/boards/rl/mini_v3/cubemx.json new file mode 100644 index 00000000..234a2771 --- /dev/null +++ b/Src/boards/rl/mini_v3/cubemx.json @@ -0,0 +1,8 @@ +{ + "name": "rl-mini-v3", + "ioc": "board/project.ioc", + "store": { + "repo": "Ilyhadev/mini_v2_node", + "tag": "cubemx-v0.1.0" + } +} diff --git a/Src/boards/rl/node_v4/board/STM32H753IIK6-V4.ioc b/Src/boards/rl/node_v4/board/project.ioc similarity index 97% rename from Src/boards/rl/node_v4/board/STM32H753IIK6-V4.ioc rename to Src/boards/rl/node_v4/board/project.ioc index fbdc1991..116e0033 100644 --- a/Src/boards/rl/node_v4/board/STM32H753IIK6-V4.ioc +++ b/Src/boards/rl/node_v4/board/project.ioc @@ -43,11 +43,18 @@ FDCAN1.RxFifo1ElmtsNbr=1 FDCAN1.TransmitPause=ENABLE FDCAN1.TxBuffersNbr=16 FDCAN1.TxFifoQueueElmtsNbr=16 -FDCAN2.CalculateBaudRateNominal=2500000 -FDCAN2.CalculateTimeBitNominal=400 -FDCAN2.CalculateTimeQuantumNominal=133.33333333333334 -FDCAN2.IPParameters=CalculateTimeQuantumNominal,CalculateTimeBitNominal,CalculateBaudRateNominal,TxBuffersNbr -FDCAN2.TxBuffersNbr=32 +FDCAN2.CalculateBaudRateNominal=250000 +FDCAN2.CalculateTimeBitNominal=4000 +FDCAN2.CalculateTimeQuantumNominal=400.0 +FDCAN2.IPParameters=CalculateTimeQuantumNominal,CalculateTimeBitNominal,CalculateBaudRateNominal,TxBuffersNbr,MessageRAMOffset,RxFifo0ElmtsNbr,RxFifo1ElmtsNbr,TxFifoQueueElmtsNbr,NominalPrescaler,NominalTimeSeg1,NominalTimeSeg2 +FDCAN2.MessageRAMOffset=1024 +FDCAN2.NominalPrescaler=48 +FDCAN2.NominalTimeSeg1=7 +FDCAN2.NominalTimeSeg2=2 +FDCAN2.RxFifo0ElmtsNbr=32 +FDCAN2.RxFifo1ElmtsNbr=0 +FDCAN2.TxBuffersNbr=16 +FDCAN2.TxFifoQueueElmtsNbr=16 File.Version=6 GPIO.groupedBy=Group By Peripherals I2C2.IPParameters=Timing @@ -478,8 +485,8 @@ ProjectManager.MainLocation=Core/Src ProjectManager.NoMain=false ProjectManager.PreviousToolchain= ProjectManager.ProjectBuild=false -ProjectManager.ProjectFileName=STM32H753IIK6-V4.ioc -ProjectManager.ProjectName=STM32H753IIK6-V4 +ProjectManager.ProjectFileName=project.ioc +ProjectManager.ProjectName=project ProjectManager.ProjectStructure= ProjectManager.RegisterCallBack= ProjectManager.StackSize=0x400 diff --git a/Src/boards/rl/node_v4/cubemx.json b/Src/boards/rl/node_v4/cubemx.json index b0b47d44..c05ba310 100644 --- a/Src/boards/rl/node_v4/cubemx.json +++ b/Src/boards/rl/node_v4/cubemx.json @@ -1,8 +1,8 @@ { "name": "rl-node-v4", - "ioc": "board/STM32H753IIK6-V4.ioc", + "ioc": "board/project.ioc", "store": { - "repo": "mini-v2-node", + "repo": "RaccoonlabDev/mini_v2_node", "tag": "cubemx-v0.1.0" } } diff --git a/Src/boards/rl/node_v4/dronecan.cmake b/Src/boards/rl/node_v4/dronecan.cmake index 843ee75d..7c7c4806 100644 --- a/Src/boards/rl/node_v4/dronecan.cmake +++ b/Src/boards/rl/node_v4/dronecan.cmake @@ -10,6 +10,7 @@ rl_include_module(application) rl_include_module(system) rl_include_module(dronecan/core) +rl_include_module(dronecan/canopen) if(NOT NODE_V4_DIAG_MINIMAL_MODULES) rl_include_module(dronecan/arming) diff --git a/Src/modules/dronecan/canopen/CMakeLists.txt b/Src/modules/dronecan/canopen/CMakeLists.txt index 142e5ed8..7aa38bc5 100644 --- a/Src/modules/dronecan/canopen/CMakeLists.txt +++ b/Src/modules/dronecan/canopen/CMakeLists.txt @@ -1,6 +1,17 @@ # Copyright (C) 2023 Dmitry Ponomarev # Distributed under the terms of the GPL v3 license, available in the file LICENSE. +if(NOT EXISTS "${NC_LIBCANOPEN_PATH}/CMakeLists.txt") + message(FATAL_ERROR "libcanopen is not found at NC_LIBCANOPEN_PATH='${NC_LIBCANOPEN_PATH}'.") +endif() + +if(NOT TARGET libcanopen) + add_subdirectory(${NC_LIBCANOPEN_PATH} ${CMAKE_BINARY_DIR}/libcanopen) +endif() + list(APPEND APPLICATION_SOURCES + ${ROOT_DIR}/Src/peripheral/can/canopen_stm32h7.cpp ${CMAKE_CURRENT_LIST_DIR}/canopen.cpp ) + +set(CONFIG_USE_CANOPEN ON) diff --git a/Src/modules/dronecan/canopen/canopen.cpp b/Src/modules/dronecan/canopen/canopen.cpp index d4342569..f47dea3d 100644 --- a/Src/modules/dronecan/canopen/canopen.cpp +++ b/Src/modules/dronecan/canopen/canopen.cpp @@ -2,44 +2,74 @@ * This program is free software under the GNU General Public License v3. * See for details. * Author: Dmitry Ponomarev + * Author: Ilia Kliantsevich */ #include "canopen.hpp" -#include "libdcnode/can_driver.h" -#include "common/algorithms.hpp" -REGISTER_MODULE(CanopenModule) +#include -void CanopenModule::init() { - set_health(Status::OK); +#include "peripheral/can/canopen_stm32h7.hpp" - if (canDriverInit(1000000, CAN_DRIVER_SECOND) < 0) { - set_health(Status::FATAL_MALFANCTION); - } +REGISTER_MODULE(CanopenModule) - if (raw_command_sub.init(raw_command_cb) < 0) { +void CanopenModule::init() { + static libcanopen::Node node(canopenGetTransportApi()); + _node = &node; + if (_node->init(CANOPEN_BITRATE, PIHER_NODE_ID) < 0) { set_health(Status::FATAL_MALFANCTION); + } else if (_node->sendNmtStart() < 0) { + set_health(Status::MAJOR_FAILURE); + } else { + _nmt_attempts = 1U; + _last_nmt_ms = HAL_GetTick(); + _logger.log_info("PIHER NMT start sent: 000#017F"); + set_health(Status::OK); } - set_mode(Mode::STANDBY); } void CanopenModule::spin_once() { - // We probably want to check TTL here -} - -void CanopenModule::raw_command_cb(const RawCommand_t& msg) { - if (msg.size < RAW_COMMAND_CHANNEL + 1) { + if (_node == nullptr) { return; } - - auto raw_command_value = msg.raw_cmd[RAW_COMMAND_CHANNEL]; - uint8_t setpoint_percent = mapFloat((float)raw_command_value, 0.0f, 8191, 0.0f, 100.0f); - - CanardCANFrame frame; - frame.id = RAW_COMMAND_CHANNEL; - frame.data[0] = setpoint_percent; - frame.data_len = 1; - - canDriverTransmit(&frame, CAN_DRIVER_SECOND); + const uint32_t now_ms = HAL_GetTick(); + if (!_reported_first_tpdo && _nmt_attempts > 0U && _nmt_attempts < MAX_NMT_ATTEMPTS && + now_ms - _last_nmt_ms >= NMT_RETRY_INTERVAL_MS) { + if (_node->sendNmtStart() < 0) { + set_health(Status::MAJOR_FAILURE); + } else { + _nmt_attempts++; + _last_nmt_ms = now_ms; + } + } + const int16_t received = + _node->spinOnce(PIHER_TPDO_ID, PIHER_TPDO_DLC, MAX_RX_FRAMES_PER_SPIN); + if (received < 0) { + set_health(Status::MAJOR_FAILURE); + return; + } + if (received > 0) { + set_health(Status::OK); + if (!_reported_first_tpdo) { + libcanopen::Frame frame{}; + if (_node->getLastTpdo(frame)) { + char message[96]{}; + (void)snprintf(message, + sizeof(message), + "PIHER active: %03X#%02X%02X%02X%02X%02X%02X%02X%02X", + frame.id, + frame.data[0], + frame.data[1], + frame.data[2], + frame.data[3], + frame.data[4], + frame.data[5], + frame.data[6], + frame.data[7]); + _logger.log_info(message); + } + _reported_first_tpdo = true; + } + } } diff --git a/Src/modules/dronecan/canopen/canopen.hpp b/Src/modules/dronecan/canopen/canopen.hpp index bd1f3ee0..7440bda7 100644 --- a/Src/modules/dronecan/canopen/canopen.hpp +++ b/Src/modules/dronecan/canopen/canopen.hpp @@ -2,35 +2,38 @@ * This program is free software under the GNU General Public License v3. * See for details. * Author: Dmitry Ponomarev + * Author: Ilia Kliantsevich */ -#ifndef SRC_MODULES_CANOPEN_HPP_ -#define SRC_MODULES_CANOPEN_HPP_ +#ifndef SRC_MODULES_DRONECAN_CANOPEN_CANOPEN_HPP_ +#define SRC_MODULES_DRONECAN_CANOPEN_CANOPEN_HPP_ +#include "common/logging.hpp" +#include "libcanopen/canopen.hpp" #include "module.hpp" -#include "subscriber.hpp" - -#ifdef __cplusplus -extern "C" { -#endif class CanopenModule : public Module { public: - CanopenModule() : Module(10.0, Protocol::DRONECAN) {} + CanopenModule() : Module(10.0F, Protocol::DRONECAN) {} void init() override; protected: void spin_once() override; private: - static void raw_command_cb(const RawCommand_t& msg); - static inline DronecanSubscriber raw_command_sub; - - static constexpr uint8_t RAW_COMMAND_CHANNEL = 0; + static constexpr uint32_t CANOPEN_BITRATE = 250000U; + static constexpr uint8_t PIHER_NODE_ID = 0x7FU; + static constexpr uint16_t PIHER_TPDO_ID = 0x1FFU; + static constexpr uint8_t PIHER_TPDO_DLC = 8U; + static constexpr uint8_t MAX_RX_FRAMES_PER_SPIN = 8U; + static constexpr uint8_t MAX_NMT_ATTEMPTS = 5U; + static constexpr uint32_t NMT_RETRY_INTERVAL_MS = 700U; + + libcanopen::Node* _node{nullptr}; + uint32_t _last_nmt_ms{0U}; + uint8_t _nmt_attempts{0U}; + bool _reported_first_tpdo{false}; + Logging _logger{"canopen"}; }; -#ifdef __cplusplus -} -#endif - -#endif // SRC_MODULES_CANOPEN_HPP_ +#endif // SRC_MODULES_DRONECAN_CANOPEN_CANOPEN_HPP_ diff --git a/Src/modules/dronecan/core/CMakeLists.txt b/Src/modules/dronecan/core/CMakeLists.txt index c21150db..f635c4cf 100644 --- a/Src/modules/dronecan/core/CMakeLists.txt +++ b/Src/modules/dronecan/core/CMakeLists.txt @@ -8,8 +8,11 @@ use_external_github_library(libdcnode submodule ${NC_LIBDCNODE_PATH} - ) -# 2. libcanver -include(${NC_LIBDCNODE_PATH}/platform_specific/${CAN_PLATFORM}/config.cmake) +if(CAN_PLATFORM STREQUAL "fdcan") + include(${ROOT_DIR}/Src/peripheral/can/CMakeLists.txt) +else() + include(${NC_LIBDCNODE_PATH}/platform_specific/${CAN_PLATFORM}/config.cmake) +endif() list(APPEND APPLICATION_SOURCES ${libparamsSrc} diff --git a/Src/modules/dronecan/core/dronecan_module.cpp b/Src/modules/dronecan/core/dronecan_module.cpp index fe672bb3..e5d53e48 100644 --- a/Src/modules/dronecan/core/dronecan_module.cpp +++ b/Src/modules/dronecan/core/dronecan_module.cpp @@ -107,7 +107,6 @@ static void setDefaultNodeName(ParamIndex_t node_name_param_idx) { } void DronecanModule::init() { - ParamsApi params_api = { .getName = paramsGetName, .isInteger = paramsIsInteger, diff --git a/Src/peripheral/can/can_stm32g0.cpp b/Src/peripheral/can/can_stm32g0.cpp index 7e2f0837..c1ebebcf 100644 --- a/Src/peripheral/can/can_stm32g0.cpp +++ b/Src/peripheral/can/can_stm32g0.cpp @@ -4,139 +4,110 @@ * Author: Dmitry Ponomarev */ -#include "peripheral/can/can.hpp" +#include "libdcnode/can_driver.h" #include #include "main.h" -#include "peripheral/can/ring_buffer.hpp" extern FDCAN_HandleTypeDef hfdcan1; -namespace HAL { +namespace { -typedef struct { - FDCAN_HandleTypeDef* handler; +struct CanDriver { FDCAN_TxHeaderTypeDef tx_header; -} CanDriver; + uint8_t rx_buf[8]; + size_t err_counter; + size_t tx_counter; + size_t rx_counter; +}; -static CanDriver can_driver{}; -static RingBuffer rx_ring_buffer; +CanDriver driver{}; +bool started = false; -void canOnRxFifo0Callback(FDCAN_HandleTypeDef* const hfdcan) { - if (hfdcan == nullptr || can_driver.handler == nullptr || hfdcan != can_driver.handler) { - return; - } +} // namespace - FDCAN_RxHeaderTypeDef rx_header{}; - uint8_t rx_buf[8]{}; - const uint8_t fifo_size = HAL_FDCAN_GetRxFifoFillLevel(hfdcan, FDCAN_RX_FIFO0); - for (uint8_t idx = 0U; idx < fifo_size; idx++) { - if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &rx_header, rx_buf) != HAL_OK) { - continue; - } - - Can::ClassicFrame frame{}; - frame.id = rx_header.Identifier; - frame.data_len = static_cast(rx_header.DataLength >> 16U); - if (frame.data_len > 8U) { - frame.data_len = 8U; - } - frame.timestamp_ns = static_cast(HAL_GetTick()) * 1000000ULL; - memcpy(frame.data, rx_buf, frame.data_len); - rx_ring_buffer.push(frame); - } +extern "C" void canDriverSetInterfaceName(const char* interface_name) { + (void)interface_name; } -Can::Can(Instance instance) : _instance(instance) {} - -Can::ErrorCode Can::init(const InitConfig& config) { - (void)config.baud_rate; - - if (_instance != Instance::INSTANCE_1) { - _stats.errors++; - return ErrorCode::INITIALIZATION_FAILED; +extern "C" int16_t canDriverInit(uint32_t can_speed, uint8_t can_driver_idx) { + (void)can_speed; + if (can_driver_idx != CAN_DRIVER_FIRST || started) { + return -1; } - can_driver.handler = &hfdcan1; - can_driver.tx_header.IdType = FDCAN_EXTENDED_ID; - can_driver.tx_header.TxFrameType = FDCAN_DATA_FRAME; - can_driver.tx_header.ErrorStateIndicator = FDCAN_ESI_ACTIVE; - can_driver.tx_header.BitRateSwitch = FDCAN_BRS_OFF; - can_driver.tx_header.FDFormat = FDCAN_CLASSIC_CAN; - can_driver.tx_header.TxEventFifoControl = FDCAN_NO_TX_EVENTS; - can_driver.tx_header.MessageMarker = 0; + driver.tx_header.IdType = FDCAN_EXTENDED_ID; + driver.tx_header.TxFrameType = FDCAN_DATA_FRAME; + driver.tx_header.ErrorStateIndicator = FDCAN_ESI_ACTIVE; + driver.tx_header.BitRateSwitch = FDCAN_BRS_OFF; + driver.tx_header.FDFormat = FDCAN_CLASSIC_CAN; + driver.tx_header.TxEventFifoControl = FDCAN_NO_TX_EVENTS; + driver.tx_header.MessageMarker = 0U; - FDCAN_FilterTypeDef filter_config; + FDCAN_FilterTypeDef filter_config{}; filter_config.IdType = FDCAN_EXTENDED_ID; - filter_config.FilterIndex = 0; + filter_config.FilterIndex = 0U; filter_config.FilterType = FDCAN_FILTER_MASK; filter_config.FilterConfig = FDCAN_FILTER_DISABLE; - if (HAL_FDCAN_ConfigFilter(can_driver.handler, &filter_config) != HAL_OK) { - _stats.errors++; - return ErrorCode::INITIALIZATION_FAILED; - } - - if (HAL_FDCAN_Start(can_driver.handler) != HAL_OK) { - _stats.errors++; - return ErrorCode::INITIALIZATION_FAILED; + if (HAL_FDCAN_ConfigFilter(&hfdcan1, &filter_config) != HAL_OK || + HAL_FDCAN_Start(&hfdcan1) != HAL_OK) { + driver.err_counter++; + return -1; } - if (HAL_FDCAN_ActivateNotification(can_driver.handler, - FDCAN_IT_RX_FIFO0_NEW_MESSAGE, - 0U) != HAL_OK) { - _stats.errors++; - return ErrorCode::INITIALIZATION_FAILED; - } - - _is_initialized = true; - return ErrorCode::SUCCESS; + started = true; + return 0; } -std::variant Can::receive() { - if (!_is_initialized) { - _stats.errors++; - return ErrorCode::INITIALIZATION_FAILED; +extern "C" int16_t canDriverReceive(CanardCANFrame* const rx_frame, + uint8_t can_driver_idx) { + if (rx_frame == nullptr || can_driver_idx != CAN_DRIVER_FIRST || !started) { + return 0; } - if (rx_ring_buffer.getSize() == 0U) { - return ErrorCode::TIMEOUT_OR_FIFO_EMPTY; + FDCAN_RxHeaderTypeDef rx_header{}; + if (HAL_FDCAN_GetRxMessage(&hfdcan1, FDCAN_RX_FIFO0, &rx_header, driver.rx_buf) != HAL_OK) { + return 0; } - __disable_irq(); - ClassicFrame frame = rx_ring_buffer.pop(); - __enable_irq(); - - _stats.rx_frames++; - return frame; + driver.rx_counter++; + rx_frame->id = (CANARD_CAN_EXT_ID_MASK & rx_header.Identifier) | CANARD_CAN_FRAME_EFF; + rx_frame->data_len = static_cast(rx_header.DataLength >> 16U); + rx_frame->iface_id = 0U; + if (rx_frame->data_len > sizeof(rx_frame->data)) { + driver.err_counter++; + return 0; + } + memcpy(rx_frame->data, driver.rx_buf, rx_frame->data_len); + return 1; } -Can::ErrorCode Can::send(const ClassicFrame& frame) { - if (!_is_initialized) { - _stats.errors++; - return ErrorCode::INITIALIZATION_FAILED; +extern "C" int16_t canDriverTransmit(const CanardCANFrame* const tx_frame, + uint8_t can_driver_idx) { + if (tx_frame == nullptr || can_driver_idx != CAN_DRIVER_FIRST || !started || + tx_frame->data_len > sizeof(tx_frame->data)) { + return 0; } - can_driver.tx_header.Identifier = frame.id; - can_driver.tx_header.DataLength = frame.data_len << 16; - - HAL_StatusTypeDef status = HAL_FDCAN_AddMessageToTxFifoQ(can_driver.handler, - &can_driver.tx_header, - (uint8_t*)frame.data); - if (status != HAL_OK) { - _stats.errors++; - return ErrorCode::TRANSMISSION_ERROR; + driver.tx_header.Identifier = tx_frame->id & CANARD_CAN_EXT_ID_MASK; + driver.tx_header.DataLength = static_cast(tx_frame->data_len) << 16U; + if (HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, + &driver.tx_header, + const_cast(tx_frame->data)) != HAL_OK) { + driver.err_counter++; + return 0; } - _stats.tx_frames++; - return ErrorCode::SUCCESS; + driver.tx_counter++; + return 1; } -} // namespace HAL +extern "C" uint64_t canDriverGetErrorCount() { + return driver.err_counter; +} -extern "C" void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef* hfdcan, - uint32_t RxFifo0ITs) { - (void)RxFifo0ITs; - HAL::canOnRxFifo0Callback(hfdcan); +extern "C" uint64_t canDriverGetRxOverflowCount() { + return 0U; } diff --git a/Src/peripheral/can/can_stm32h7.cpp b/Src/peripheral/can/can_stm32h7.cpp index 741f4495..68cfa6ec 100644 --- a/Src/peripheral/can/can_stm32h7.cpp +++ b/Src/peripheral/can/can_stm32h7.cpp @@ -4,262 +4,116 @@ * Author: Ilia Kliantsevich */ -#include "peripheral/can/can.hpp" +#include "libdcnode/can_driver.h" #include #include "main.h" -#ifndef NUM_OF_CAN_BUSES - #define NUM_OF_CAN_BUSES 1 -#endif - -#ifndef DRONECAN_FDCAN_PRIMARY - #define DRONECAN_FDCAN_PRIMARY 1 -#endif - extern FDCAN_HandleTypeDef hfdcan1; -extern FDCAN_HandleTypeDef hfdcan2; - -namespace HAL { namespace { struct CanDriver { - FDCAN_HandleTypeDef* handler; FDCAN_TxHeaderTypeDef tx_header; uint8_t rx_buf[8]; - bool initialized; uint32_t err_counter; uint32_t tx_counter; uint32_t rx_counter; }; -CanDriver can_drivers[NUM_OF_CAN_BUSES] = { -#if DRONECAN_FDCAN_PRIMARY == 2 - {.handler = &hfdcan2, .tx_header = {}, .rx_buf = {}, .initialized = false, - .err_counter = 0, .tx_counter = 0, .rx_counter = 0}, -#if NUM_OF_CAN_BUSES >= 2 - {.handler = &hfdcan1, .tx_header = {}, .rx_buf = {}, .initialized = false, - .err_counter = 0, .tx_counter = 0, .rx_counter = 0} -#endif -#else - {.handler = &hfdcan1, .tx_header = {}, .rx_buf = {}, .initialized = false, - .err_counter = 0, .tx_counter = 0, .rx_counter = 0}, -#if NUM_OF_CAN_BUSES >= 2 - {.handler = &hfdcan2, .tx_header = {}, .rx_buf = {}, .initialized = false, - .err_counter = 0, .tx_counter = 0, .rx_counter = 0} -#endif -#endif -}; - -uint8_t dlcToLength(const uint32_t dlc) { - switch (dlc) { - case FDCAN_DLC_BYTES_0: - return 0U; - case FDCAN_DLC_BYTES_1: - return 1U; - case FDCAN_DLC_BYTES_2: - return 2U; - case FDCAN_DLC_BYTES_3: - return 3U; - case FDCAN_DLC_BYTES_4: - return 4U; - case FDCAN_DLC_BYTES_5: - return 5U; - case FDCAN_DLC_BYTES_6: - return 6U; - case FDCAN_DLC_BYTES_7: - return 7U; - case FDCAN_DLC_BYTES_8: - default: - return 8U; - } +CanDriver driver{}; +bool started = false; + +void initializeTxHeader() { + driver.tx_header = {}; + driver.tx_header.IdType = FDCAN_EXTENDED_ID; + driver.tx_header.TxFrameType = FDCAN_DATA_FRAME; + driver.tx_header.ErrorStateIndicator = FDCAN_ESI_ACTIVE; + driver.tx_header.BitRateSwitch = FDCAN_BRS_OFF; + driver.tx_header.FDFormat = FDCAN_CLASSIC_CAN; + driver.tx_header.TxEventFifoControl = FDCAN_NO_TX_EVENTS; } -uint32_t lengthToDlc(const uint8_t length) { - switch (length) { - case 0U: - return FDCAN_DLC_BYTES_0; - case 1U: - return FDCAN_DLC_BYTES_1; - case 2U: - return FDCAN_DLC_BYTES_2; - case 3U: - return FDCAN_DLC_BYTES_3; - case 4U: - return FDCAN_DLC_BYTES_4; - case 5U: - return FDCAN_DLC_BYTES_5; - case 6U: - return FDCAN_DLC_BYTES_6; - case 7U: - return FDCAN_DLC_BYTES_7; - case 8U: - default: - return FDCAN_DLC_BYTES_8; - } -} +} // namespace -void initTxHeader(FDCAN_TxHeaderTypeDef& tx_header) { - tx_header.IdType = FDCAN_EXTENDED_ID; - tx_header.TxFrameType = FDCAN_DATA_FRAME; - tx_header.ErrorStateIndicator = FDCAN_ESI_ACTIVE; - tx_header.BitRateSwitch = FDCAN_BRS_OFF; - tx_header.FDFormat = FDCAN_CLASSIC_CAN; - tx_header.TxEventFifoControl = FDCAN_NO_TX_EVENTS; - tx_header.MessageMarker = 0; +extern "C" void canDriverSetInterfaceName(const char* interface_name) { + (void)interface_name; } -bool initPhysicalCan(const size_t physical_idx) { - if (physical_idx >= NUM_OF_CAN_BUSES || can_drivers[physical_idx].handler == nullptr) { - return false; - } - if (can_drivers[physical_idx].initialized) { - return true; - } - - initTxHeader(can_drivers[physical_idx].tx_header); - - HAL_StatusTypeDef status = HAL_FDCAN_ConfigGlobalFilter( - can_drivers[physical_idx].handler, - FDCAN_ACCEPT_IN_RX_FIFO0, - FDCAN_ACCEPT_IN_RX_FIFO0, - FDCAN_REJECT_REMOTE, - FDCAN_REJECT_REMOTE); - if (status != HAL_OK) { - can_drivers[physical_idx].err_counter++; - return false; +extern "C" int16_t canDriverInit(uint32_t can_speed, uint8_t can_driver_idx) { + (void)can_speed; + if (can_driver_idx != CAN_DRIVER_FIRST || started) { + return -1; } - - status = HAL_FDCAN_Start(can_drivers[physical_idx].handler); - if (status != HAL_OK) { - can_drivers[physical_idx].err_counter++; - return false; + initializeTxHeader(); + if (HAL_FDCAN_ConfigGlobalFilter(&hfdcan1, + FDCAN_ACCEPT_IN_RX_FIFO0, + FDCAN_ACCEPT_IN_RX_FIFO0, + FDCAN_REJECT_REMOTE, + FDCAN_REJECT_REMOTE) != HAL_OK || + HAL_FDCAN_Start(&hfdcan1) != HAL_OK) { + driver.err_counter++; + return -1; } - - can_drivers[physical_idx].initialized = true; - return true; + started = true; + return 0; } -size_t instanceToFirstPhysical(const Can::Instance instance) { - if (instance == Can::Instance::INSTANCE_2 && NUM_OF_CAN_BUSES >= 2) { - return 1U; +extern "C" int16_t canDriverReceive(CanardCANFrame* const rx_frame, + uint8_t can_driver_idx) { + if (rx_frame == nullptr || can_driver_idx != CAN_DRIVER_FIRST || !started) { + return 0; } - return 0U; -} - -size_t instanceToPhysicalCount(const Can::Instance instance) { - if (instance == Can::Instance::INSTANCE_1) { - return NUM_OF_CAN_BUSES; + FDCAN_RxHeaderTypeDef header{}; + if (HAL_FDCAN_GetRxMessage(&hfdcan1, FDCAN_RX_FIFO0, &header, driver.rx_buf) != HAL_OK) { + return 0; } - return (NUM_OF_CAN_BUSES >= 2) ? 1U : 0U; -} - -} // namespace - -Can::Can(Instance instance) : _instance(instance) {} - -Can::ErrorCode Can::init(const InitConfig& config) { - (void)config.baud_rate; - - const size_t first_physical_idx = instanceToFirstPhysical(_instance); - const size_t physical_count = instanceToPhysicalCount(_instance); - if (physical_count == 0U || first_physical_idx >= NUM_OF_CAN_BUSES) { - _stats.errors++; - return ErrorCode::INITIALIZATION_FAILED; + if (header.IdType != FDCAN_EXTENDED_ID || header.RxFrameType != FDCAN_DATA_FRAME || + header.FDFormat != FDCAN_CLASSIC_CAN) { + driver.err_counter++; + return 0; } - - for (size_t offset = 0U; offset < physical_count; offset++) { - if (!initPhysicalCan(first_physical_idx + offset)) { - _stats.errors++; - return ErrorCode::INITIALIZATION_FAILED; - } + const uint8_t data_len = static_cast(header.DataLength); + if (data_len > sizeof(rx_frame->data)) { + driver.err_counter++; + return 0; } - - _is_initialized = true; - return ErrorCode::SUCCESS; + rx_frame->id = (header.Identifier & CANARD_CAN_EXT_ID_MASK) | CANARD_CAN_FRAME_EFF; + rx_frame->data_len = data_len; + rx_frame->iface_id = 0U; + memcpy(rx_frame->data, driver.rx_buf, data_len); + driver.rx_counter++; + return 1; } -std::variant Can::receive() { - if (!_is_initialized) { - _stats.errors++; - return ErrorCode::INITIALIZATION_FAILED; +extern "C" int16_t canDriverTransmit(const CanardCANFrame* const tx_frame, + uint8_t can_driver_idx) { + if (tx_frame == nullptr || can_driver_idx != CAN_DRIVER_FIRST || !started || + tx_frame->data_len > sizeof(tx_frame->data)) { + return 0; } - - const size_t first_physical_idx = instanceToFirstPhysical(_instance); - const size_t physical_count = instanceToPhysicalCount(_instance); - static size_t next_physical_offset = 0U; - - for (size_t attempt = 0U; attempt < physical_count; attempt++) { - const size_t physical_idx = - first_physical_idx + ((next_physical_offset + attempt) % physical_count); - CanDriver& driver = can_drivers[physical_idx]; - if (!driver.initialized) { - continue; - } - - FDCAN_RxHeaderTypeDef rx_header{}; - const HAL_StatusTypeDef status = HAL_FDCAN_GetRxMessage(driver.handler, - FDCAN_RX_FIFO0, - &rx_header, - driver.rx_buf); - if (status != HAL_OK) { - continue; - } - - ClassicFrame frame{}; - frame.id = rx_header.Identifier; - frame.data_len = dlcToLength(rx_header.DataLength); - frame.timestamp_ns = static_cast(HAL_GetTick()) * 1000000ULL; - memcpy(frame.data, driver.rx_buf, frame.data_len); - - driver.rx_counter++; - _stats.rx_frames++; - next_physical_offset = (physical_idx - first_physical_idx + 1U) % physical_count; - return frame; + driver.tx_header.Identifier = tx_frame->id & CANARD_CAN_EXT_ID_MASK; + driver.tx_header.DataLength = tx_frame->data_len; + if (HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, + &driver.tx_header, + const_cast(tx_frame->data)) != HAL_OK) { + driver.err_counter++; + return 0; } - - return ErrorCode::TIMEOUT_OR_FIFO_EMPTY; + driver.tx_counter++; + return 1; } -Can::ErrorCode Can::send(const ClassicFrame& frame) { - if (!_is_initialized) { - _stats.errors++; - return ErrorCode::INITIALIZATION_FAILED; - } - - const size_t first_physical_idx = instanceToFirstPhysical(_instance); - const size_t physical_count = instanceToPhysicalCount(_instance); - bool sent = false; - - for (size_t offset = 0U; offset < physical_count; offset++) { - CanDriver& driver = can_drivers[first_physical_idx + offset]; - if (!driver.initialized) { - continue; - } - - driver.tx_header.Identifier = frame.id; - driver.tx_header.DataLength = lengthToDlc(frame.data_len); - - const HAL_StatusTypeDef status = HAL_FDCAN_AddMessageToTxFifoQ(driver.handler, - &driver.tx_header, - (uint8_t*)(frame.data)); - if (status == HAL_OK) { - driver.tx_counter++; - sent = true; - } else { - driver.err_counter++; - } - } - - if (!sent) { - _stats.errors++; - return ErrorCode::TRANSMISSION_ERROR; - } - - _stats.tx_frames++; - return ErrorCode::SUCCESS; +extern "C" uint64_t canDriverGetErrorCount() { + FDCAN_ProtocolStatusTypeDef protocol{}; + FDCAN_ErrorCountersTypeDef counters{}; + (void)HAL_FDCAN_GetProtocolStatus(&hfdcan1, &protocol); + (void)HAL_FDCAN_GetErrorCounters(&hfdcan1, &counters); + return driver.err_counter + protocol.BusOff + protocol.Warning + protocol.ErrorPassive + + counters.TxErrorCnt + counters.RxErrorCnt; } -} // namespace HAL +extern "C" uint64_t canDriverGetRxOverflowCount() { + return 0U; +} diff --git a/Src/peripheral/can/canopen_stm32h7.cpp b/Src/peripheral/can/canopen_stm32h7.cpp new file mode 100644 index 00000000..3c257aeb --- /dev/null +++ b/Src/peripheral/can/canopen_stm32h7.cpp @@ -0,0 +1,122 @@ +#include "peripheral/can/canopen_stm32h7.hpp" + +#include + +#include "main.h" + +extern "C" FDCAN_HandleTypeDef hfdcan2; + +namespace { + +struct Context { + uint32_t errors; + bool started; +}; + +Context context{}; + +HAL_StatusTypeDef sendFrame(const FDCAN_TxHeaderTypeDef& header, const uint8_t* data) { + if (hfdcan2.Init.TxFifoQueueElmtsNbr > 0U) { + return HAL_FDCAN_AddMessageToTxFifoQ( + &hfdcan2, &header, const_cast(data)); + } + + constexpr uint32_t MAX_TX_BUFFERS = 32U; + const uint32_t buffer_count = hfdcan2.Init.TxBuffersNbr < MAX_TX_BUFFERS + ? hfdcan2.Init.TxBuffersNbr + : MAX_TX_BUFFERS; + for (uint32_t idx = 0U; idx < buffer_count; idx++) { + const uint32_t buffer = 1UL << idx; + if (HAL_FDCAN_IsTxBufferMessagePending(&hfdcan2, buffer) == 0U && + HAL_FDCAN_AddMessageToTxBuffer(&hfdcan2, &header, data, buffer) == HAL_OK) { + return HAL_FDCAN_EnableTxBufferRequest(&hfdcan2, buffer); + } + } + return HAL_ERROR; +} + +int16_t init(void* raw_context, uint32_t bitrate) { + (void)bitrate; + auto* ctx = static_cast(raw_context); + if (ctx == nullptr || ctx->started) { + return -1; + } + if (HAL_FDCAN_ConfigGlobalFilter(&hfdcan2, + FDCAN_ACCEPT_IN_RX_FIFO0, + FDCAN_REJECT, + FDCAN_REJECT_REMOTE, + FDCAN_REJECT_REMOTE) != HAL_OK || + HAL_FDCAN_Start(&hfdcan2) != HAL_OK) { + ctx->errors++; + return -1; + } + ctx->started = true; + return 0; +} + +int16_t send(void* raw_context, const libcanopen::Frame& frame) { + auto* ctx = static_cast(raw_context); + if (ctx == nullptr || !ctx->started || frame.id > 0x7FFU || frame.data_len > 8U || + frame.type != libcanopen::FrameType::DATA) { + return -1; + } + + FDCAN_TxHeaderTypeDef header{}; + header.Identifier = frame.id; + header.IdType = FDCAN_STANDARD_ID; + header.TxFrameType = FDCAN_DATA_FRAME; + header.DataLength = frame.data_len; + header.ErrorStateIndicator = FDCAN_ESI_ACTIVE; + header.BitRateSwitch = FDCAN_BRS_OFF; + header.FDFormat = FDCAN_CLASSIC_CAN; + header.TxEventFifoControl = FDCAN_NO_TX_EVENTS; + if (sendFrame(header, frame.data) != HAL_OK) { + ctx->errors++; + return -1; + } + return 1; +} + +int16_t receive(void* raw_context, libcanopen::Frame& frame) { + auto* ctx = static_cast(raw_context); + if (ctx == nullptr || !ctx->started) { + return -1; + } + + FDCAN_RxHeaderTypeDef header{}; + uint8_t data[8]{}; + if (HAL_FDCAN_GetRxMessage(&hfdcan2, FDCAN_RX_FIFO0, &header, data) != HAL_OK) { + return 0; + } + const uint8_t data_len = static_cast(header.DataLength); + if (header.IdType != FDCAN_STANDARD_ID || header.RxFrameType != FDCAN_DATA_FRAME || + header.FDFormat != FDCAN_CLASSIC_CAN || header.Identifier > 0x7FFU || + data_len > sizeof(frame.data)) { + ctx->errors++; + return 0; + } + + frame.id = static_cast(header.Identifier); + frame.data_len = data_len; + frame.type = libcanopen::FrameType::DATA; + frame.timestamp_us = static_cast(HAL_GetTick()) * 1000ULL; + memcpy(frame.data, data, data_len); + return 1; +} + +uint64_t getTimeUs(void* raw_context) { + (void)raw_context; + return static_cast(HAL_GetTick()) * 1000ULL; +} + +} // namespace + +libcanopen::TransportApi canopenGetTransportApi() { + return { + .context = &context, + .init = init, + .send = send, + .receive = receive, + .get_time_us = getTimeUs, + }; +} diff --git a/Src/peripheral/can/canopen_stm32h7.hpp b/Src/peripheral/can/canopen_stm32h7.hpp new file mode 100644 index 00000000..ca800e9f --- /dev/null +++ b/Src/peripheral/can/canopen_stm32h7.hpp @@ -0,0 +1,8 @@ +#ifndef SRC_PERIPHERAL_CAN_CANOPEN_STM32H7_HPP_ +#define SRC_PERIPHERAL_CAN_CANOPEN_STM32H7_HPP_ + +#include "libcanopen/canopen.hpp" + +libcanopen::TransportApi canopenGetTransportApi(); + +#endif // SRC_PERIPHERAL_CAN_CANOPEN_STM32H7_HPP_ diff --git a/Src/platform/stm32g0b1/CMakeLists.txt b/Src/platform/stm32g0b1/CMakeLists.txt index 2e758d22..255c9f5a 100644 --- a/Src/platform/stm32g0b1/CMakeLists.txt +++ b/Src/platform/stm32g0b1/CMakeLists.txt @@ -23,9 +23,12 @@ add_executable(${EXECUTABLE} ${coreSources} ${driversSources} + ${cmsisDspSources} ${startupFile} ) +set_source_files_properties(${cmsisDspSources} PROPERTIES COMPILE_OPTIONS "-Wno-error") + target_include_directories(${EXECUTABLE} PRIVATE ${BUILD_SRC_DIR} ${APPLICATION_HEADERS} @@ -33,7 +36,8 @@ target_include_directories(${EXECUTABLE} PRIVATE ${CMAKE_CURRENT_LIST_DIR} ${stm32cubeMxProjectPath}/Core/Inc ${stm32cubeMxProjectPath}/Drivers/CMSIS/Include - ${stm32cubeMxProjectPath}/Drivers/CMSIS/DSP/Include + ${cmsisDspPath}/Include + ${cmsisDspPath}/PrivateInclude ${stm32cubeMxProjectPath}/Drivers/CMSIS/Device/ST/STM32F1xx/Include ${stm32cubeMxProjectPath}/Drivers/STM32F1xx_HAL_Driver/Inc diff --git a/Src/platform/stm32h753xx/CMakeLists.txt b/Src/platform/stm32h753xx/CMakeLists.txt index 1fc27be5..0cfc6291 100644 --- a/Src/platform/stm32h753xx/CMakeLists.txt +++ b/Src/platform/stm32h753xx/CMakeLists.txt @@ -41,6 +41,9 @@ target_include_directories(${EXECUTABLE} PRIVATE if(CAN_PROTOCOL STREQUAL "dronecan" OR CAN_PROTOCOL STREQUAL "both") target_link_libraries(${EXECUTABLE} PRIVATE libdcnode::libdcnode) endif() +if(CONFIG_USE_CANOPEN) + target_link_libraries(${EXECUTABLE} PRIVATE libcanopen::libcanopen) +endif() if(CAN_PROTOCOL STREQUAL "cyphal" OR CAN_PROTOCOL STREQUAL "both") target_link_libraries(${EXECUTABLE} PRIVATE libcpnode::libcpnode) endif() diff --git a/cmake/create_binary_with_meaningful_name.cmake b/cmake/create_binary_with_meaningful_name.cmake index c1c95b5d..6e741595 100644 --- a/cmake/create_binary_with_meaningful_name.cmake +++ b/cmake/create_binary_with_meaningful_name.cmake @@ -21,7 +21,7 @@ elseif(DEFINED NC_APP_SRC_DIR AND NOT NC_APP_SRC_DIR STREQUAL "") get_filename_component(APP_ROOT_DIR "${NC_APP_SRC_DIR}" DIRECTORY) set(DESTINATION_DIR "${APP_ROOT_DIR}/release") else() - set(DESTINATION_DIR "${BUILD_ROOT_DIR}/release") + set(DESTINATION_DIR "${ROOT_DIR}/release") endif() if(DEFINED NC_RELEASE_APP_ROOT_DIR AND NOT NC_RELEASE_APP_ROOT_DIR STREQUAL "") diff --git a/cmake/toolchain-stm32g0b1.cmake b/cmake/toolchain-stm32g0b1.cmake index 1f0b7d31..78cac77c 100644 --- a/cmake/toolchain-stm32g0b1.cmake +++ b/cmake/toolchain-stm32g0b1.cmake @@ -53,6 +53,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) # Preprocessor Definitions # ================================== add_compile_definitions( + ARM_MATH_CM0PLUS USE_HAL_DRIVER STM32G0B1xx ) diff --git a/direct_piher_canopen.py b/direct_piher_canopen.py new file mode 100644 index 00000000..d8c4e938 --- /dev/null +++ b/direct_piher_canopen.py @@ -0,0 +1,280 @@ +#!/usr/bin/env python3 +"""Direct LAWICEL/CANUSB test for PIHER PST360G2 CANopen.""" + +import argparse +import time + +import serial + + +BITRATE_COMMANDS = { + 10000: "S0", + 20000: "S1", + 50000: "S2", + 100000: "S3", + 125000: "S4", + 250000: "S5", + 500000: "S6", + 800000: "S7", + 1000000: "S8", +} + + +def parse_int(value): + return int(value, 0) + + +def parse_hex_payload(value): + value = value.replace(":", "").replace(" ", "") + if len(value) % 2: + raise argparse.ArgumentTypeError("payload must have an even number of hex digits") + payload = bytes.fromhex(value) + if len(payload) > 8: + raise argparse.ArgumentTypeError("classic CAN payload cannot exceed 8 bytes") + return payload + + +def parse_channel(value): + if "@" not in value: + return value, 1_000_000 + + port, baudrate = value.rsplit("@", 1) + return port, int(baudrate) + + +def read_response(ser, timeout): + deadline = time.monotonic() + timeout + response = bytearray() + + while time.monotonic() < deadline: + if ser.in_waiting: + byte = ser.read(1) + if not byte: + continue + response += byte + if byte == b"\r": + break + else: + time.sleep(0.001) + + return bytes(response) + + +def send_command(ser, command, timeout, expect_response=True): + ser.write(command.encode("ascii") + b"\r") + ser.flush() + + if not expect_response: + print(f"{command:<14} -> ") + return b"" + + response = read_response(ser, timeout) + printable = response.replace(b"\r", b"\\r") + print(f"{command:<14} -> {printable!r}") + return response + + +def standard_frame_command(can_id, payload): + return f"t{can_id:03X}{len(payload):X}{payload.hex().upper()}" + + +def extended_frame_command(can_id, payload): + return f"T{can_id:08X}{len(payload):X}{payload.hex().upper()}" + + +def parse_canusb_frame(response): + try: + frame = response.decode("ascii").strip() + except UnicodeDecodeError: + return None + + if len(frame) < 5: + return None + + if frame[0] == "t": + can_id_digits = 3 + is_extended = False + elif frame[0] == "T": + can_id_digits = 8 + is_extended = True + else: + return None + + dlc_pos = 1 + can_id_digits + if len(frame) < dlc_pos + 1: + return None + + try: + can_id = int(frame[1:dlc_pos], 16) + dlc = int(frame[dlc_pos], 16) + data = bytes.fromhex(frame[dlc_pos + 1 : dlc_pos + 1 + dlc * 2]) + except ValueError: + return None + + if len(data) != dlc: + return None + + return { + "raw": frame, + "can_id": can_id, + "dlc": dlc, + "data": data, + "is_extended": is_extended, + } + + +def describe_piher_pdo(frame): + if frame["is_extended"] or frame["can_id"] != 0x1FF or frame["dlc"] != 8: + return "" + + data = frame["data"] + angle_raw = (data[0] << 8) | data[1] + angle_deg = angle_raw * 360.0 / 4095.0 + return f" angle_raw={angle_raw} angle_deg={angle_deg:.2f}" + + +def init_canusb(ser, bitrate, timeout): + for _ in range(3): + send_command(ser, "", timeout) + + version = send_command(ser, "V", timeout) + if not version.startswith(b"V"): + raise RuntimeError("CANUSB did not return a version response") + + send_command(ser, "C", timeout) + + bitrate_command = BITRATE_COMMANDS.get(bitrate) + if bitrate_command is None: + supported = ", ".join(str(rate) for rate in sorted(BITRATE_COMMANDS)) + raise RuntimeError(f"unsupported bitrate {bitrate}; choose one of {supported}") + + if send_command(ser, bitrate_command, timeout) != b"\r": + raise RuntimeError(f"CANUSB rejected bitrate command {bitrate_command}") + + if send_command(ser, "O", timeout) != b"\r": + raise RuntimeError("CANUSB rejected open command") + + send_command(ser, "F", timeout) + + +def listen(ser, duration, timeout, expected_can_id=None): + deadline = time.monotonic() + duration + frames = [] + + while time.monotonic() < deadline: + response = read_response(ser, min(timeout, max(0.0, deadline - time.monotonic()))) + if response: + printable = response.replace(b"\r", b"\\r") + frame = parse_canusb_frame(response) + if frame: + frames.append(frame) + suffix = describe_piher_pdo(frame) + print( + f"RX -> {printable!r} " + f"id=0x{frame['can_id']:X} dlc={frame['dlc']} data={frame['data'].hex(' ').upper()}{suffix}" + ) + else: + print(f"RX -> {printable!r}") + + if expected_can_id is None: + return frames + + return [frame for frame in frames if frame["can_id"] == expected_can_id] + + +def startup_piher(ser, args): + startup_payload = bytes([0x01, args.node_id]) + startup_command = extended_frame_command(0x00000000, startup_payload) + + print("Checking whether PIHER is already operational...") + pdo_frames = listen(ser, args.verify_timeout, args.timeout, expected_can_id=0x1FF) + if len(pdo_frames) >= args.min_pdo: + print(f"PIHER already operational: received {len(pdo_frames)} TPDO frames") + return True + + print( + f"Starting PIHER with extended-wrapper NMT hack: {startup_command} " + f"(logical NMT start node 0x{args.node_id:02X})" + ) + total_pdo_frames = len(pdo_frames) + + for attempt in range(1, args.startup_retries + 1): + response = send_command(ser, startup_command, args.timeout) + if response != b"Z\r": + printable = response.replace(b"\r", b"\\r") if response else response + print(f"Startup command not accepted on attempt {attempt}: {printable!r}") + else: + print(f"Startup command accepted on attempt {attempt}") + + pdo_frames = listen(ser, args.verify_timeout, args.timeout, expected_can_id=0x1FF) + total_pdo_frames += len(pdo_frames) + if len(pdo_frames) >= args.min_pdo: + print(f"PIHER startup verified: received {len(pdo_frames)} TPDO frames after attempt {attempt}") + return True + + time.sleep(args.gap) + + print(f"PIHER startup not verified: received {total_pdo_frames} total TPDO frames") + return False + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--channel", + default="/dev/serial/by-id/usb-STMicroelectronics_STM32_STLink_066AFF505372485067051653-if02@1000000", + ) + parser.add_argument("--bitrate", type=int, default=250000) + parser.add_argument("--timeout", type=float, default=0.5) + parser.add_argument("--listen", type=float, default=0.3) + parser.add_argument("--count", type=int, default=20) + parser.add_argument("--gap", type=float, default=0.2) + parser.add_argument("--node-id", type=parse_int, default=0x7F) + parser.add_argument("--can-id", type=parse_int, default=0x000) + parser.add_argument("--payload", type=parse_hex_payload, default=bytes([0x01, 0x7F])) + parser.add_argument("--extended", action="store_true") + parser.add_argument( + "--raw-send", + action="store_true", + help="send --can-id/--payload repeatedly instead of PIHER startup/verify", + ) + parser.add_argument("--startup-retries", type=int, default=5) + parser.add_argument("--verify-timeout", type=float, default=0.7) + parser.add_argument("--min-pdo", type=int, default=3) + args = parser.parse_args() + + port, serial_baudrate = parse_channel(args.channel) + command_factory = extended_frame_command if args.extended else standard_frame_command + + print(f"Connecting: {port} @ {serial_baudrate}") + print(f"CAN bitrate: {args.bitrate}") + + with serial.Serial(port, baudrate=serial_baudrate, timeout=args.timeout) as ser: + ser.reset_input_buffer() + init_canusb(ser, args.bitrate, args.timeout) + + if not args.raw_send: + ok = startup_piher(ser, args) + send_command(ser, "F", args.timeout) + send_command(ser, "C", args.timeout) + raise SystemExit(0 if ok else 1) + + for attempt in range(1, args.count + 1): + payload = args.payload + if args.can_id == 0x000 and payload == bytes([0x01, 0x7F]): + payload = bytes([0x01, args.node_id]) + + command = command_factory(args.can_id, payload) + response = send_command(ser, command, args.timeout) + if response not in (b"z\r", b"Z\r"): + print(f"TX command was not accepted on attempt {attempt}") + + listen(ser, args.listen, args.timeout) + time.sleep(args.gap) + + send_command(ser, "F", args.timeout) + send_command(ser, "C", args.timeout) + + +if __name__ == "__main__": + main() diff --git a/docs/build_system.md b/docs/build_system.md index 045b06db..d4eaef1a 100644 --- a/docs/build_system.md +++ b/docs/build_system.md @@ -41,6 +41,9 @@ Naming rule: Under the hood each target runs `scripts/build.sh`, which (re)configures CMake only when an input changed and then compiles. +Final firmware binaries are copied to `release/`. Intermediate objects and +board-specific build trees remain under `build/`. + ## 3. Options Pass these as `VAR=value` before the target when you need them: diff --git a/docs/cubemx.md b/docs/cubemx.md index 6fe7a049..d4193d60 100644 --- a/docs/cubemx.md +++ b/docs/cubemx.md @@ -52,6 +52,11 @@ third_party/node-core/scripts/cubemx.py ensure --manifest src/boards///cub `ensure` resolves in order: existing project → local cache → local CubeMX → download from the release. +Pass `--release-only` when a target must always consume the published project. +This ignores an installed local CubeMX, an existing generated project, and the +local archive cache. Mini v3 currently uses this as a deliberate temporary +policy so its optional IMU/DSP generation path cannot slow ordinary builds. + ## Wiring into a build Point the board at the restored project and run `ensure` before building. In the diff --git a/scripts/cubemx.py b/scripts/cubemx.py index ee6715c5..9b05f863 100755 --- a/scripts/cubemx.py +++ b/scripts/cubemx.py @@ -164,11 +164,14 @@ def _download_via_gh(manifest: Manifest, archive_name: str, dest: Path) -> bool: print(f"Downloading {archive_name} via gh from {manifest.repo}@{manifest.tag}") # `gh release download` is unreliable for a single private asset, so resolve # the asset id via the API and stream it as octet-stream (works with GH_TOKEN). - asset_id = subprocess.run( + release = subprocess.run( ["gh", "api", f"repos/{manifest.repo}/releases/tags/{manifest.tag}", "--jq", f'.assets[] | select(.name=="{archive_name}") | .id'], check=False, capture_output=True, text=True, - ).stdout.strip() + ) + if release.returncode != 0: + return False + asset_id = release.stdout.strip() if not asset_id: return False with dest.open("wb") as handle: @@ -199,6 +202,15 @@ def cmd_ensure(args: argparse.Namespace) -> None: ioc_hash = manifest.ioc_hash cache_dir = Path(args.cache_dir).resolve() + if args.release_only: + if download_from_store(manifest, ioc_hash, out_dir): + print(f"Restored from release: {out_dir}") + return + fail( + f"no release archive for '{manifest.name}' ({ioc_hash}) at " + f"{manifest.repo}@{manifest.tag}." + ) + if stamp_matches(out_dir, ioc_hash): print(f"CubeMX project is up to date: {out_dir}") return @@ -317,6 +329,8 @@ def add_common(p: argparse.ArgumentParser) -> None: p_ensure = sub.add_parser("ensure", help="restore or generate the project") add_common(p_ensure) + p_ensure.add_argument("--release-only", action="store_true", + help="always restore from the configured GitHub release") p_ensure.set_defaults(func=cmd_ensure) p_package = sub.add_parser("package", help="generate and archive the project")