cmake_minimum_required(VERSION 3.22) include(ExternalProject) include(cmake/Utilities.cmake) include(cmake/GetGitRevisionDescription.cmake) include(cmake/ProjectVersion.cmake) include(cmake/Littlefs.cmake) include(cmake/Options.cmake) project( Buddy LANGUAGES C CXX ASM VERSION ${PROJECT_VERSION} ) # Sets Python3_FIND_STRATEGY to "LOCATION" cmake_policy(SET CMP0094 NEW) # Enable initialization of subprojects without their submodules cmake_policy(SET CMP0097 NEW) if(NOT CMAKE_CROSSCOMPILING) # # If we are not crosscompiling, include `utils` with host tools. # add_subdirectory(utils) endif() include(ProjectOptions.cmake) # Check GCC Version get_recommended_gcc_version(RECOMMENDED_TOOLCHAIN_VERSION) if(CMAKE_CROSSCOMPILING AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL ${RECOMMENDED_TOOLCHAIN_VERSION} ) message(WARNING "Recommended ARM toolchain is ${RECOMMENDED_TOOLCHAIN_VERSION}" ", but you have ${CMAKE_CXX_COMPILER_VERSION}" ) elseif(NOT CMAKE_CROSSCOMPILING AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") message( WARNING "Recommended compiler for host tools and unittests is GCC, you have ${CMAKE_CXX_COMPILER_ID}." ) endif() # eclipse sets those variables, so lets just use them so we don't get a warning about unused # variables set(unused "${CMAKE_VERBOSE_MAKEFILE} ${CMAKE_RULE_MESSAGES}") set(CMAKE_CXX_STANDARD 23) # enable all warnings (well, not all, but some) add_compile_options(-Wall -Wsign-compare) # make all paths stored inside firmware relative add_compile_options(-ffile-prefix-map=${CMAKE_SOURCE_DIR}=.) add_compile_options( $<$:-Wno-register> $<$:-Wno-volatile> $<$:-fno-rtti> ) # append custom C/C++ flags if(CUSTOM_COMPILE_OPTIONS) string(REPLACE " " ";" CUSTOM_COMPILE_OPTIONS "${CUSTOM_COMPILE_OPTIONS}") add_compile_options(${CUSTOM_COMPILE_OPTIONS}) endif() # find bootloader.bin location if(BOOTLOADER STREQUAL "YES") string(TOLOWER ${PRINTER} printer_low) set(BOOTLOADER_VARIANT "${printer_low}") endif() # # BuddyHeaders # # This library provides headers in the /include directory. When a library requires a configuration # header, e.g. STM32::USBHost requires usbh_conf.h, we can just place the header to /include and # then add BuddyHeaders as a dependency to STM32::USBHost. # # TODO: Refactor this to make it clear what header files are associated with which targets. # add_library(BuddyHeaders INTERFACE) target_include_directories( BuddyHeaders INTERFACE include include/usb_host include/usb_device include/marlin src/hw src/common src/persistent_stores src/persistent_stores/store_instances src/guiconfig src/lang src/common/utils ${OPTIONS_INCLUDE_DIR} ) if(MCU MATCHES "STM32G0") target_include_directories( BuddyHeaders BEFORE INTERFACE include/stm32g0_hal include/device/stm32g0 ) target_link_libraries(BuddyHeaders INTERFACE STM32G0::HAL STM32G0xx::CMSIS) elseif(MCU MATCHES "STM32F4") target_include_directories( BuddyHeaders BEFORE INTERFACE include/stm32f4_hal include/device/stm32f4 ) target_link_libraries(BuddyHeaders INTERFACE STM32F4::HAL STM32F4xx::CMSIS) elseif(MCU MATCHES "STM32H5") target_include_directories( BuddyHeaders BEFORE INTERFACE include/stm32h5_hal include/device/stm32h5 ) target_link_libraries(BuddyHeaders INTERFACE STM32H5::HAL STM32H5xx::CMSIS) else() message(FATAL_ERROR "Undefined HAL for ${MCU}") endif() target_include_directories(BuddyHeaders INTERFACE include) target_link_libraries(BuddyHeaders INTERFACE FreeRTOS::FreeRTOS) # Define printer type and version set(PRINTER_VERSION "1") # default version set(PRINTER_SUBVERSION "0") # default subversion # The PRINTER_TYPE is (besides being used in the code) being stored in the .bbf and checked by the # bootloader (which contains the same "enum" as below). Preferably do not use it on an interface # though, there should be a better number to use there, e.g. the PRINTER_CODE (USB PID) if(PRINTER STREQUAL "MK4") set(PRINTER_TYPE "1") set(PRINTER_VERSION "4") set(PRINTER_CODE "13") elseif(PRINTER STREQUAL "MK3.5") set(PRINTER_TYPE "1") set(PRINTER_VERSION "3") set(PRINTER_SUBVERSION "5") set(PRINTER_CODE "23") elseif(PRINTER STREQUAL "MINI") set(PRINTER_TYPE "2") set(PRINTER_CODE "12") elseif(PRINTER STREQUAL "XL") set(PRINTER_TYPE "3") set(PRINTER_CODE "17") elseif(PRINTER STREQUAL "iX") set(PRINTER_TYPE "4") set(PRINTER_CODE "16") elseif(PRINTER STREQUAL "XL_DEV_KIT") set(PRINTER_TYPE "5") set(PRINTER_CODE "18") elseif(PRINTER STREQUAL "COREONE") set(PRINTER_TYPE "7") set(PRINTER_CODE "31") else() message(FATAL_ERROR "Unknown printer \"${PRINTER}\".") endif() add_library(printers INTERFACE) target_include_directories(printers INTERFACE ${CMAKE_SOURCE_DIR}/include) target_compile_definitions( printers INTERFACE PRINTER_TYPE=${PRINTER_TYPE} PRINTER_VERSION=${PRINTER_VERSION} PRINTER_SUBVERSION=${PRINTER_SUBVERSION} ) target_compile_definitions( BuddyHeaders INTERFACE PRINTER_CODE=${PRINTER_CODE} BOARD=BOARD_${BOARD} BOARD_VERSION_MAJOR=${BOARD_VERSION_MAJOR} BOARD_VERSION_MINOR=${BOARD_VERSION_MINOR} BOARD_VERSION_PATCH=${BOARD_VERSION_PATCH} MARLIN_DISABLE_INFINITE_LOOP PROCESS_CUSTOM_GCODE ) target_link_libraries(BuddyHeaders INTERFACE printers) if(MCU MATCHES "STM32F4") target_compile_definitions(BuddyHeaders INTERFACE STM32GENERIC STM32F4 STM32F4xx) elseif(MCU MATCHES "STM32G0") target_compile_definitions(BuddyHeaders INTERFACE STM32GENERIC STM32G0 STM32G0xx) elseif(MCU MATCHES "STM32H5") target_compile_definitions(BuddyHeaders INTERFACE STM32GENERIC STM32H5 STM32H5xx) else() message(FATAL_ERROR "Undefined macros for ${MCU}") endif() # # Configure STMicroelectronics Libraries # # STM32::USBHost add_library(STM32_USBHost_Config ALIAS BuddyHeaders) # STM32::Utilities::CPU add_library(STM32_Utilities_CPU_Config ALIAS BuddyHeaders) # # Configure SEGGER # add_library(Segger_Config INTERFACE) target_include_directories(Segger_Config INTERFACE include/segger src/segger) target_link_libraries(Segger_Config INTERFACE BuddyHeaders) # # Configure LwIP # add_library(LwIP_Config INTERFACE) target_link_libraries(LwIP_Config INTERFACE BuddyHeaders logging) # # Configure FatFs # add_library(FatFs_Config INTERFACE) target_link_libraries(FatFs_Config INTERFACE BuddyHeaders logging STM32::USBHost) # # Configure Marlin # add_library(Marlin_Config INTERFACE) # TODO: fix dependency on src/common and src/gui target_include_directories( Marlin_Config INTERFACE include/marlin src/common src/gui src/marlin_stubs/include ) target_include_directories(Marlin_Config INTERFACE src/common/selftest) target_link_libraries(Marlin_Config INTERFACE BuddyHeaders FreeRTOS::FreeRTOS) # # Configure tinyusb # add_library(tinyusb_dependencies INTERFACE) target_include_directories(tinyusb_dependencies INTERFACE include/tinyusb) target_link_libraries(tinyusb_dependencies INTERFACE FreeRTOS::FreeRTOS) # # Global Compiler & Linker Configuration # # include symbols add_compile_options(-g) # optimizations if(CMAKE_BUILD_TYPE STREQUAL "Debug") if(MCU MATCHES "STM32F40" OR MCU MATCHES "STM32G0" OR MCU MATCHES "STM32H5" OR PRINTER STREQUAL "MK4" OR PRINTER STREQUAL "MK3.5" OR PRINTER STREQUAL "XL" OR PRINTER STREQUAL "iX" OR PRINTER STREQUAL "COREONE" ) add_compile_options(-Og -ggdb3) else() # O0 should be compiled with -fomit-frame-pointer to make sure there is enough registers for asm # functions add_compile_options(-O0 -fomit-frame-pointer) endif() else() add_compile_options(-Os) endif() if(CMAKE_CROSSCOMPILING) # mcu related settings set(MCU_FLAGS -mthumb) if(MCU MATCHES "STM32F4") list(APPEND MCU_FLAGS -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16) elseif(MCU MATCHES "STM32G0") list(APPEND MCU_FLAGS -mcpu=cortex-m0plus -mfloat-abi=soft) elseif(MCU MATCHES "STM32H5") list(APPEND MCU_FLAGS -mcpu=cortex-m33 -mfloat-abi=hard -mfpu=fpv5-sp-d16) else() message(FATAL_ERROR "Unknown MCU_FLAGS for ${MCU}") endif() add_compile_options(${MCU_FLAGS}) add_link_options(${MCU_FLAGS}) # better FreeRTOS support add_link_options(-Wl,--undefined=init_task) # disable exceptions and related metadata add_compile_options(-fno-exceptions -fno-unwind-tables) add_link_options(-Wl,--defsym,__exidx_start=0,--defsym,__exidx_end=0) # wrap _dtoa to ensure formatting of floats isn't being called from ISR add_link_options(-Wl,--wrap=_dtoa_r) # use custom printf implementation instead of the one in newlib (see lib/printf) add_link_options( -Wl,--defsym=printf=printf_,--defsym=sprintf=sprintf_,--defsym=snprintf=snprintf_,--defsym=vprintf=vprintf_,--defsym=vsnprintf=vsnprintf_ ) # wrap _dtoa to ensure formatting of floats isn't being called from ISR add_link_options(-Wl,--wrap=_dtoa_r) # disable warnings about RWX sections due to CrashCatcher's "safe" page add_link_options(-Wl,--no-warn-rwx-segments) endif() # split and gc sections add_compile_options(-ffunction-sections -fdata-sections) add_link_options(-Wl,--gc-sections) # support _DEBUG macro (some code uses to recognize debug builds) if(CMAKE_BUILD_TYPE STREQUAL "Debug") add_compile_definitions(_DEBUG) endif() # disable unaligned access # # * Otherwise, with optimizations turned on, the firmware crashes on startup. # # The main problem was caused by zlib, thus this switch was propagated directly to it, it seems to # be solved now And let's keep this line commented here for emergency situations ;) # # add_compile_options(-mno-unaligned-access) # # Import definitions of all libraries # add_subdirectory(lib) # # Build Puppy Firmwares # if(BOARD IN_LIST BUDDY_BOARDS AND HAS_PUPPIES_BOOTLOADER) string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_lower) string(TOLOWER "${PRINTER}" PRINTER_lower) if(NOT DWARF_BINARY_PATH AND HAS_DWARF) get_filename_component( DWARF_BINARY_DIR "${DWARF_BINARY_DIR}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}" ) get_filename_component(DWARF_BINARY_PATH "firmware.bin" REALPATH BASE_DIR "${DWARF_BINARY_DIR}") ExternalProject_Add( dwarf SOURCE_DIR "${DWARF_SOURCE_DIR}" BINARY_DIR "${DWARF_BINARY_DIR}" CMAKE_ARGS --preset xl-dwarf_${CMAKE_BUILD_TYPE_lower}_boot -B "${DWARF_BINARY_DIR}" INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping install step" BUILD_BYPRODUCTS "${DWARF_BINARY_PATH}" "${DWARF_BINARY_DIR}/firmware" STEP_TARGETS build USES_TERMINAL_BUILD TRUE USES_TERMINAL_INSTALL FALSE BUILD_ALWAYS TRUE ) endif() if(NOT MODULARBED_BINARY_PATH AND HAS_MODULARBED) get_filename_component( MODULARBED_BINARY_DIR "${MODULARBED_BINARY_DIR}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}" ) get_filename_component( MODULARBED_BINARY_PATH "firmware.bin" REALPATH BASE_DIR "${MODULARBED_BINARY_DIR}" ) ExternalProject_Add( modularbed SOURCE_DIR "${MODULARBED_SOURCE_DIR}" BINARY_DIR "${MODULARBED_BINARY_DIR}" CMAKE_ARGS --preset ${PRINTER_lower}-modularbed_${CMAKE_BUILD_TYPE_lower}_boot -B "${MODULARBED_BINARY_DIR}" INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping install step" BUILD_BYPRODUCTS "${MODULARBED_BINARY_PATH}" "${MODULARBED_BINARY_DIR}/firmware" STEP_TARGETS build USES_TERMINAL_BUILD TRUE USES_TERMINAL_INSTALL FALSE BUILD_ALWAYS TRUE ) endif() if(NOT XBUDDY_EXTENSION_BINARY_PATH AND HAS_XBUDDY_EXTENSION) get_filename_component( XBUDDY_EXTENSION_BINARY_DIR "${XBUDDY_EXTENSION_BINARY_DIR}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}" ) get_filename_component( XBUDDY_EXTENSION_BINARY_PATH "firmware.bin" REALPATH BASE_DIR "${XBUDDY_EXTENSION_BINARY_DIR}" ) ExternalProject_Add( xbuddy_extension SOURCE_DIR "${XBUDDY_EXTENSION_SOURCE_DIR}" BINARY_DIR "${XBUDDY_EXTENSION_BINARY_DIR}" CMAKE_ARGS --preset ${PRINTER_lower}-xbuddy-extension_${CMAKE_BUILD_TYPE_lower}_boot -B "${XBUDDY_EXTENSION_BINARY_DIR}" INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping install step" BUILD_BYPRODUCTS "${XBUDDY_EXTENSION_BINARY_PATH}" "${XBUDDY_EXTENSION_BINARY_DIR}/firmware" STEP_TARGETS build USES_TERMINAL_BUILD TRUE USES_TERMINAL_INSTALL FALSE BUILD_ALWAYS TRUE ) endif() endif() # Unittests if(NOT CMAKE_CROSSCOMPILING) option(UNITTESTS_ENABLE "Enable building of unittest" ON) if(UNITTESTS_ENABLE) enable_testing() add_subdirectory(tests) endif() endif() # std::rand/random if(BOARD IN_LIST BUDDY_BOARDS) set(ENABLE_HW_STD_RAND TRUE) set(RANDOM_CPP random_hw.cpp) else() set(ENABLE_HW_STD_RAND FALSE) set(RANDOM_CPP random_sw.cpp) endif() # # Buddy firmware # add_executable(firmware) add_subdirectory(src) target_compile_options( firmware PRIVATE $<$:-Wextra> $<$:-Wno-expansion-to-defined> $<$:-Wno-format-zero-length> $<$:-Wno-non-virtual-dtor> $<$:-Werror=delete-non-virtual-dtor> $<$:-Wduplicated-cond> $<$:-Wlogical-op> $<$:-Wnull-dereference> $<$:-Wshadow=compatible-local> $<$:-Wvla> $<$:-Wno-psabi> -Wdouble-promotion ) if(ENABLE_HW_STD_RAND) # Wrap rand and replace the implementation with one using HW RNG target_link_options(firmware PUBLIC -Wl,--wrap=rand) # Wrap srand, do not implement the wrapper -> throws error when referenced in the code. rand() # uses HW RNG now, so it cannot be seeded target_link_options(firmware PUBLIC -Wl,--wrap=srand) endif() # Appending fw descriptor (with calculated fingerprint) to the ELF if(BOARD STREQUAL "DWARF" OR BOARD STREQUAL "MODULARBED" OR BOARD STREQUAL "XBUDDY_EXTENSION" ) add_custom_command( TARGET firmware POST_BUILD # Generate a binary without fingerprint COMMAND "${CMAKE_OBJCOPY}" -O binary -S "$" "firmware_no_descriptor.bin" # Create the fw descriptor with calculated fingerprint of binary COMMAND "${Python3_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/utils/gen_puppies_descriptor.py" "firmware_no_descriptor.bin" "fw_descriptor.bin" # Set the firmware's fw descriptor in the firmware's ELF file COMMAND "${CMAKE_OBJCOPY}" "--update-section" ".fw_descriptor=fw_descriptor.bin" "$" "$" VERBATIM ) endif() # generate firmware.bin file objcopy(firmware "binary" ".bin") # generate linker map file target_link_options(firmware PUBLIC -Wl,-Map=firmware.map,--print-memory-usage) # link time optimizations if(NOT DEVELOPER_MODE) set_target_properties(firmware PROPERTIES INTERPROCEDURAL_OPTIMIZATION True) endif() # inform about the firmware's size in terminal report_size(firmware) # generate bbf if needed if(GENERATE_BBF) message(STATUS "Configured to generate .bbf version of the firmware.") message(STATUS "Signing Key: ${SIGNING_KEY}") if(PROJECT_VERSION_SUFFIX) if(NOT "${PROJECT_VERSION_SUFFIX}" MATCHES "\\+${BUILD_NUMBER}") message(WARNING "The generated .bbf is configured to use the build number ${BUILD_NUMBER}, but the version suffix (${PROJECT_VERSION_SUFFIX}) does not contain +${BUILD_NUMBER}. Are you sure you know what you are doing?" ) endif() endif() set(resource_images) set(resource_image_names) if(RESOURCES) list(APPEND resource_images resources-image) list(APPEND resource_image_names "RESOURCES_IMAGE") endif() if(BOOTLOADER_UPDATE) list(APPEND resource_images resources-bootloader-image) list(APPEND resource_image_names "RESOURCES_BOOTLOADER_IMAGE") endif() pack_firmware( firmware FW_VERSION "${PROJECT_VERSION}${PROJECT_VERSION_SUFFIX_SHORT}" BUILD_NUMBER "${BUILD_NUMBER}" PRINTER_TYPE "${PRINTER_TYPE}" PRINTER_VERSION "${PRINTER_VERSION}" PRINTER_SUBVERSION "${PRINTER_SUBVERSION}" SIGNING_KEY "${SIGNING_KEY}" RESOURCE_IMAGES ${resource_images} RESOURCE_IMAGE_NAMES ${resource_image_names} ) elseif(SIGNING_KEY) message(WARNING "SIGNING_KEY specified but BBF generation is not enabled.") endif() # generate .dfu version if requested if(GENERATE_DFU) if(BOOTLOADER) set(firmware_addr "0x08020000") if(BOOTLOADER STREQUAL "YES") get_dependency_directory("bootloader-${BOOTLOADER_VARIANT}" bootloader_dir) set(bootloader_input "0x08000000:${bootloader_dir}/bootloader.bin") endif() set(firmware_input "0x08020000:${CMAKE_BINARY_DIR}/firmware.bbf") else() set(firmware_input "0x08000000:${CMAKE_BINARY_DIR}/firmware.bin") endif() create_dfu( TARGET firmware INPUT "${bootloader_input}" "${firmware_input}" OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/firmware.dfu" ) endif() if(BOARD IN_LIST BUDDY_BOARDS) target_link_libraries( firmware PRIVATE BuddyHeaders CrashCatcher Marlin Arduino::Core Arduino::TMCStepper LwIP FatFs littlefs STM32::USBHost inih::inih $<$:WUI> jsmn::jsmn QR Buddy::Lang CppStdExtensions printf::printf sysbase Segger tinyusb::tinyusb $<$:MMU2::MMU2> mbedTLS $<$:lightmodbus> freertos error_codes error_codes_mmu # TODO once MMU is not necessary for the build, replace with: # $<$:error_codes_mmu> bgcode_core heatshrink_decoder version ) elseif(BOARD STREQUAL "DWARF") target_link_libraries( firmware PRIVATE Arduino::Core Arduino::TMCStepper BuddyHeaders CrashCatcher error_codes freertos Marlin printf::printf ) elseif(BOARD STREQUAL "MODULARBED") target_link_libraries( firmware PRIVATE BuddyHeaders CrashCatcher error_codes freertos printf::printf ) elseif(BOARD STREQUAL "XBUDDY_EXTENSION") # let us manage the libraries where needed, not globally elseif(BOARD STREQUAL "XL_DEV_KIT_XLB") target_link_libraries( firmware PRIVATE BuddyHeaders Marlin Arduino::Core Arduino::TMCStepper inih::inih CppStdExtensions printf::printf sysbase Segger $<$:MMU2::MMU2> mbedTLS $<$:lightmodbus> freertos error_codes error_codes_mmu # TODO once MMU is not necessary for the build, replace with: # $<$:error_codes_mmu> bgcode_core # todo: remove heatshrink_decoder # todo: remove version ) endif()