Search code examples
ubuntucmakearmtoolchain

How can I set up the GCC ARM compiler for a STM32F412xx controller on Linux (Ubuntu) and build a demo project via CMake?


Setting up the toolchain to build and deploying firmware is challenging. There are several ways. I am looking for a demo in which the gcc-arm-none-eabi compiler is installed into Ubuntu, a toolchain file for a STM32F412xx controller is defined, and a demo project like "Hello, World!" is build through CMake for an STM32F412xx controller.

What I have done so far:

echo install arm x-compiler
sudo add-apt-repository ppa:team-gcc-arm-embedded/ppa
sudo apt-get update
sudo apt-get install gcc-arm-none-eabi
echo install st link
sudo apt-get install git build-essential libusb-1.0.0-dev cmake
echo install eclipse
sudo apt-get install openjdk-11-jdk

Any proposals?


Solution

  • Building a project takes following steps:

    1. Install the gcc-arm-none-eabi toolchain

    It can be found on Launchpad or at ARM. Download the Linux installation tarball, extract, and add the /bin folder in the extracted folder to the PATH environment variable.

    2. Install supporting packages

    Install CMake, build-essential libusb-1.0.0-dev cmake, stlink, and OpenOCD from your distribution.

    3. Create a CMake file for the toolchain

    For the STM32F412 MPU I put mine below:

    include(CMakeForceCompiler)
    
    ##############################
    # gnu-none-ebai install dir
    # /usr/lib/arm-none-eabi
    #
    # stm32f412
    # Features:
    #    - fpu
    #    - dsp
    #
    # instructionset for libs:
    # /usr/lib/arm-none-eabi/lib/armv7e-m/<soft/fpu>/{FPU precision}
    
    
    set(CMAKE_C_COMPILER arm-none-eabi-gcc)
    set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
    set(OBJCOPY arm-none-eabi-objcopy)
    
    set(CMAKE_SYSTEM_NAME Linux)
    set(CMAKE_SYSTEM_VERSION 1)
    set(CMAKE_SYSTEM_PROCESSOR arm)
    
    set(COMMON_FLAGS "-march=armv7e-m -mtune=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -mthumb -mthumb-interwork -Og -ffunction-sections -fdata-sections -fno-move-loop-invariants")
    set(LINKER_SCRIPTS "-T ${CMAKE_SOURCE_DIR}/ldscripts/STM32F412RG.ld -T ${CMAKE_SOURCE_DIR}/ldscripts/sections.ld -T ${CMAKE_SOURCE_DIR}/ldscripts/libs.ld")
    
    set(CMAKE_CXX_FLAGS "${COMMON_FLAGS} -std=c++11" CACHE INTERNAL "")
    set(CMAKE_C_FLAGS "${COMMON_FLAGS} -std=gnu99" CACHE INTERNAL "")
    set(CMAKE_ASM_FLAGS "${COMMON_FLAGS}" CACHE INTERNAL "")
    set(CMAKE_EXE_LINKER_FLAGS  "-W -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -nostartfiles --specs=nosys.specs -ffunction-sections -fdata-sections -fno-move-loop-invariants ${LINKER_SCRIPTS}" CACHE INTERNAL "")
    
    
    #
    include_directories(BEFORE SYSTEM "/usr/lib/arm-none-eabi/include/")
    
    list(APPEND TOOLCHAIN_EXTRA_LIBDIR
        "/usr/lib/arm-none-eabi/lib/armv7e-m/fpu"
    )
    
    link_directories(${TOOLCHAIN_EXTRA_LIBDIR})
    
    set(CMAKE_C_COMPILER_WORKS 1)
    set(CMAKE_CXX_COMPILER_WORKS 1)
    

    4. Add/copy the linker scripts

    4.1 sections.ld

    /*
     * Default linker script for Cortex-M (it includes specifics for STM32F[34]xx).
     *
     * To make use of the multi-region initialisations, define
     * OS_INCLUDE_STARTUP_INIT_MULTIPLE_RAM_SECTIONS for the _startup.c file.
     */
    
    /*
     * The '__stack' definition is required by crt0, do not remove it.
     */
    __stack = ORIGIN(RAM) + LENGTH(RAM);
    
    _estack = __stack;     /* STM specific definition */
    
    
    /*
     * Default stack sizes.
     * These are used by the startup in order to allocate stacks
     * for the different modes.
     */
    
    __Main_Stack_Size = 1024 ;
    
    PROVIDE ( _Main_Stack_Size = __Main_Stack_Size ) ;
    
    __Main_Stack_Limit = __stack  - __Main_Stack_Size ;
    
    /* "PROVIDE" allows to easily override these values from an
     * object file or the command line. */
    PROVIDE ( _Main_Stack_Limit = __Main_Stack_Limit ) ;
    
    /*
     * There will be a link error if there is not this amount of
     * RAM free at the end.
     */
    _Minimum_Stack_Size = 256 ;
    
    /*
     * Default heap definitions.
     * The heap start immediately after the last statically allocated
     * .sbss/.noinit section, and extends up to the main stack limit.
     */
    PROVIDE ( _Heap_Begin = _end_noinit ) ;
    PROVIDE ( _Heap_Limit = __stack - __Main_Stack_Size ) ;
    
    /*
     * The entry point is informative, for debuggers and simulators,
     * since the Cortex-M vector points to it anyway.
     */
    ENTRY(_start)
    
    
    /* Sections Definitions */
    
    SECTIONS
    {
        /*
         * For Cortex-M devices, the beginning of the startup code is stored in
         * the .isr_vector section, which goes to FLASH.
         */
        .isr_vector : ALIGN(4)
        {
            FILL(0xFF)
    
            __vectors_start = ABSOLUTE(.) ;
            __vectors_start__ = ABSOLUTE(.) ; /* STM specific definition */
            KEEP(*(.isr_vector))         /* Interrupt vectors */
    
            KEEP(*(.cfmconfig))            /* Freescale configuration words */
    
            /*
             * This section is here for convenience, to store the
             * startup code at the beginning of the flash area, hoping that
             * this will increase the readability of the listing.
             */
            *(.after_vectors .after_vectors.*)    /* Startup code and ISR */
            /* by StJ */
            __vectors_end = ABSOLUTE(.) ;
        } >FLASH
    
        .inits : ALIGN(4)
        {
            /*
             * Memory regions initialisation arrays.
             *
             * Thee are two kinds of arrays for each RAM region, one for
             * data and one for bss. Each is iterated at startup and the
             * region initialisation is performed.
             *
             * The data array includes:
             * - from (LOADADDR())
             * - region_begin (ADDR())
             * - region_end (ADDR()+SIZEOF())
             *
             * The bss array includes:
             * - region_begin (ADDR())
             * - region_end (ADDR()+SIZEOF())
             *
             * WARNING: It is mandatory that the regions are word aligned,
             * since the initialisation code works only on words.
             */
    
            __data_regions_array_start = .;
    
            LONG(LOADADDR(.data));
            LONG(ADDR(.data));
            LONG(ADDR(.data)+SIZEOF(.data));
    
            LONG(LOADADDR(.data_CCMRAM));
            LONG(ADDR(.data_CCMRAM));
            LONG(ADDR(.data_CCMRAM)+SIZEOF(.data_CCMRAM));
    
            __data_regions_array_end = .;
    
            __bss_regions_array_start = .;
    
            LONG(ADDR(.bss));
            LONG(ADDR(.bss)+SIZEOF(.bss));
    
            LONG(ADDR(.bss_CCMRAM));
            LONG(ADDR(.bss_CCMRAM)+SIZEOF(.bss_CCMRAM));
    
            __bss_regions_array_end = .;
    
            /* End of memory regions initialisation arrays. */
    
            /*
             * These are the old initialisation sections, intended to contain
             * naked code, with the prologue/epilogue added by crti.o/crtn.o
             * when linking with startup files. The standalone startup code
             * currently does not run these, better use the init arrays below.
             */
            KEEP(*(.init))
            KEEP(*(.fini))
    
            . = ALIGN(4);
    
            /*
             * The preinit code, i.e. an array of pointers to initialisation
             * functions to be performed before constructors.
             */
            PROVIDE_HIDDEN (__preinit_array_start = .);
    
            /*
             * Used to run the SystemInit() before anything else.
             */
            KEEP(*(.preinit_array_sysinit .preinit_array_sysinit.*))
    
            /*
             * Used for other platform inits.
             */
            KEEP(*(.preinit_array_platform .preinit_array_platform.*))
    
            /*
             * The application inits. If you need to enforce some order in
             * execution, create new sections, as before.
             */
            KEEP(*(.preinit_array .preinit_array.*))
    
            PROVIDE_HIDDEN (__preinit_array_end = .);
    
            . = ALIGN(4);
    
            /*
             * The init code, i.e. an array of pointers to static constructors.
             */
            PROVIDE_HIDDEN (__init_array_start = .);
            KEEP(*(SORT(.init_array.*)))
            KEEP(*(.init_array))
            PROVIDE_HIDDEN (__init_array_end = .);
    
            . = ALIGN(4);
    
            /*
             * The fini code, i.e. an array of pointers to static destructors.
             */
            PROVIDE_HIDDEN (__fini_array_start = .);
            KEEP(*(SORT(.fini_array.*)))
            KEEP(*(.fini_array))
            PROVIDE_HIDDEN (__fini_array_end = .);
    
        } >FLASH
    
        /*
         * For some STRx devices, the beginning of the startup code
         * is stored in the .flashtext section, which goes to FLASH.
         */
        .flashtext : ALIGN(4)
        {
            *(.flashtext .flashtext.*)    /* Startup code */
        } >FLASH
    
    
        /*
         * The program code is stored in the .text section,
         * which goes to FLASH.
         */
        .text : ALIGN(4)
        {
            *(.text .text.*)            /* All remaining code */
    
             /* read-only data (constants) */
            *(.rodata .rodata.* .constdata .constdata.*)
    
            *(vtable)                    /* C++ virtual tables */
    
            KEEP(*(.eh_frame*))
    
            /*
             * Stub sections generated by the linker, to glue together
             * ARM and Thumb code. .glue_7 is used for ARM code calling
             * Thumb code, and .glue_7t is used for Thumb code calling
             * ARM code. Apparently always generated by the linker, for some
             * architectures, so better leave them here.
             */
            *(.glue_7)
            *(.glue_7t)
    
        } >FLASH
    
        /* ARM magic sections */
        .ARM.extab : ALIGN(4)
           {
           *(.ARM.extab* .gnu.linkonce.armextab.*)
           } > FLASH
    
        . = ALIGN(4);
           __exidx_start = .;
           .ARM.exidx : ALIGN(4)
           {
           *(.ARM.exidx* .gnu.linkonce.armexidx.*)
           } > FLASH
           __exidx_end = .;
    
        . = ALIGN(4);
        _etext = .;
        __etext = .;
    
        /* MEMORY_ARRAY */
        /*
        .ROarraySection :
        {
             *(.ROarraySection .ROarraySection.*)
        } >MEMORY_ARRAY
        */
    
        /*
         * The secondary initialised data section.
         */
        .data_CCMRAM : ALIGN(4)
        {
           FILL(0xFF)
           *(.data.CCMRAM .data.CCMRAM.*)
           . = ALIGN(4) ;
        } > CCMRAM AT>FLASH
    
        /*
         * This address is used by the startup code to
         * initialise the .data section.
         */
        _sidata = LOADADDR(.data);
    
        /*
         * The initialised data section.
         *
         * The program executes knowing that the data is in the RAM
         * but the loader puts the initial values in the FLASH (inidata).
         * It is one task of the startup to copy the initial values from
         * FLASH to RAM.
         */
        .data : ALIGN(4)
        {
            FILL(0xFF)
            /* This is used by the startup code to initialise the .data section */
            _sdata = . ;            /* STM specific definition */
            __data_start__ = . ;
            *(.data_begin .data_begin.*)
    
            *(.data .data.*)
    
            *(.data_end .data_end.*)
            . = ALIGN(4);
    
            /* This is used by the startup code to initialise the .data section */
            _edata = . ;            /* STM specific definition */
            __data_end__ = . ;
    
        } >RAM AT>FLASH
    
        /*
         * The uninitialised data sections. NOLOAD is used to avoid
         * the "section `.bss' type changed to PROGBITS" warning
         */
    
        /* The secondary uninitialised data section. */
        .bss_CCMRAM (NOLOAD) : ALIGN(4)
        {
            *(.bss.CCMRAM .bss.CCMRAM.*)
        } > CCMRAM
    
        /* The primary uninitialised data section. */
        .bss (NOLOAD) : ALIGN(4)
        {
            __bss_start__ = .;         /* Standard newlib definition */
            _sbss = .;              /* STM specific definition */
            *(.bss_begin .bss_begin.*)
    
            *(.bss .bss.*)
            *(COMMON)
    
            *(.bss_end .bss_end.*)
            . = ALIGN(4);
            __bss_end__ = .;        /* Standard newlib definition */
            _ebss = . ;             /* STM specific definition */
        } >RAM
    
        .noinit_CCMRAM (NOLOAD) : ALIGN(4)
        {
            *(.noinit.CCMRAM .noinit.CCMRAM.*)
        } > CCMRAM
    
        .noinit (NOLOAD) : ALIGN(4)
        {
            _noinit = .;
    
            *(.noinit .noinit.*)
    
             . = ALIGN(4) ;
            _end_noinit = .;
        } > RAM
    
        /* Mandatory to be word aligned, _sbrk assumes this */
        PROVIDE ( end = _end_noinit ); /* was _ebss */
        PROVIDE ( _end = _end_noinit );
        PROVIDE ( __end = _end_noinit );
        PROVIDE ( __end__ = _end_noinit );
    
        /*
         * Used for validation only, do not allocate anything here!
         *
         * This is just to check that there is enough RAM left for the Main
         * stack. It should generate an error if it's full.
         */
        ._check_stack : ALIGN(4)
        {
            . = . + _Minimum_Stack_Size ;
        } >RAM
    
        /*
         * The FLASH Bank1.
         * The C or assembly source must explicitly place the code
         * or data there using the "section" attribute.
         */
        .b1text : ALIGN(4)
        {
            *(.b1text)                   /* Remaining code */
            *(.b1rodata)                 /* Read-only data (constants) */
            *(.b1rodata.*)
        } >FLASHB1
    
        /*
         * The EXTMEM.
         * The C or assembly source must explicitly place the code or data there
         * using the "section" attribute.
         */
    
        /* EXTMEM Bank0 */
        .eb0text : ALIGN(4)
        {
            *(.eb0text)                   /* Remaining code */
            *(.eb0rodata)                 /* Read-only data (constants) */
            *(.eb0rodata.*)
        } >EXTMEMB0
    
        /* EXTMEM Bank1 */
        .eb1text : ALIGN(4)
        {
            *(.eb1text)                   /* Remaining code */
            *(.eb1rodata)                 /* Read-only data (constants) */
            *(.eb1rodata.*)
        } >EXTMEMB1
    
        /* EXTMEM Bank2 */
        .eb2text : ALIGN(4)
        {
            *(.eb2text)                   /* Remaining code */
            *(.eb2rodata)                 /* Read-only data (constants) */
            *(.eb2rodata.*)
        } >EXTMEMB2
    
        /* EXTMEM Bank0 */
        .eb3text : ALIGN(4)
        {
            *(.eb3text)                   /* Remaining code */
            *(.eb3rodata)                 /* Read-only data (constants) */
            *(.eb3rodata.*)
        } >EXTMEMB3
    
    
        /* After that there are only debugging sections. */
    
        /* This can remove the debugging information from the standard libraries */
        /*
        DISCARD :
        {
         libc.a ( * )
         libm.a ( * )
         libgcc.a ( * )
         }
         */
    
        /* Stabs debugging sections.  */
        .stab          0 : { *(.stab) }
        .stabstr       0 : { *(.stabstr) }
        .stab.excl     0 : { *(.stab.excl) }
        .stab.exclstr  0 : { *(.stab.exclstr) }
        .stab.index    0 : { *(.stab.index) }
        .stab.indexstr 0 : { *(.stab.indexstr) }
        .comment       0 : { *(.comment) }
        /*
         * DWARF debug sections.
         * Symbols in the DWARF debugging sections are relative to the beginning
         * of the section so we begin them at 0.
         */
        /* DWARF 1 */
        .debug          0 : { *(.debug) }
        .line           0 : { *(.line) }
        /* GNU DWARF 1 extensions */
        .debug_srcinfo  0 : { *(.debug_srcinfo) }
        .debug_sfnames  0 : { *(.debug_sfnames) }
        /* DWARF 1.1 and DWARF 2 */
        .debug_aranges  0 : { *(.debug_aranges) }
        .debug_pubnames 0 : { *(.debug_pubnames) }
        /* DWARF 2 */
        .debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
        .debug_abbrev   0 : { *(.debug_abbrev) }
        .debug_line     0 : { *(.debug_line) }
        .debug_frame    0 : { *(.debug_frame) }
        .debug_str      0 : { *(.debug_str) }
        .debug_loc      0 : { *(.debug_loc) }
        .debug_macinfo  0 : { *(.debug_macinfo) }
        /* SGI/MIPS DWARF 2 extensions */
        .debug_weaknames 0 : { *(.debug_weaknames) }
        .debug_funcnames 0 : { *(.debug_funcnames) }
        .debug_typenames 0 : { *(.debug_typenames) }
        .debug_varnames  0 : { *(.debug_varnames) }
    }
    
    /* Some adds */
    __stack_end__ = _estack;
    __RAM_segment_end__ = __stack ;
    __vectors_load_start__ = __vectors_start;
    __vectors_load_end__ = __vectors_end;
    _start = main;
    

    4.2 lib.ld

    /*
     * Placeholder to list other libraries required by the application.
    
    GROUP(
    )
    
     */
    

    4.3 STM32F412RG.ld

    /*
     * Memory Spaces Definitions.
     *
     * Need modifying for a specific board.
     *   FLASH.ORIGIN: starting address of flash
     *   FLASH.LENGTH: length of flash
     *   RAM.ORIGIN: starting address of RAM bank 0
     *   RAM.LENGTH: length of RAM bank 0
     *
     * The values below can be addressed in further linker scripts
     * using functions like 'ORIGIN(RAM)' or 'LENGTH(RAM)'.
     */
    
    MEMORY
    {
      RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 256K
      CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 0K
      FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
      FLASHB1 (rx) : ORIGIN = 0x00000000, LENGTH = 0
      EXTMEMB0 (rx) : ORIGIN = 0x00000000, LENGTH = 0
      EXTMEMB1 (rx) : ORIGIN = 0x00000000, LENGTH = 0
      EXTMEMB2 (rx) : ORIGIN = 0x00000000, LENGTH = 0
      EXTMEMB3 (rx) : ORIGIN = 0x00000000, LENGTH = 0
      MEMORY_ARRAY (xrw)  : ORIGIN = 0x20002000, LENGTH = 32
    }
    
    /*
     * For external ram use something like:
    
       RAM (xrw) : ORIGIN = 0x64000000, LENGTH = 2048K
    
     */
    

    5. Instruction

    1. Create your CMake project - this example is taken from a bootloader

      cmake_minimum_required(VERSION 3.10)
      set(BL "BOOTLOADER")
      set(${BL}_VERSION_MAJOR 1)
      set(${BL}_VERSION_MINOR 4)
      set(${BL}_VERSION_REVISION 1)
      set(${BL}_SOVERSION 1)
      list(APPEND ${BL}_DEFINES
          USE_HAL_DRIVER
          STM32F412Rx
      )
      set(LINKER_FLAGS_BL "-Xlinker -Map=bl.map -Ttext 0x08020000")
      list(APPEND ${BL}_inc
          ${CMAKE_CURRENT_SOURCE_DIR}/inc/
          ${CMAKE_CURRENT_SOURCE_DIR}/drivers/CMSIS/Include
          ${CMAKE_CURRENT_SOURCE_DIR}/drivers/CMSIS/Device/ST/STM32F4xx/Include
          ${CMAKE_CURRENT_SOURCE_DIR}/drivers/STM32F4xx_HAL_Driver/Inc
          ${CMAKE_CURRENT_SOURCE_DIR}/drivers/STM32F4xx_HAL_Driver/Inc/Legacy
          ${CMAKE_CURRENT_SOURCE_DIR}/middlewares/ST/STM32_USB_Device_Library/Core/Inc
          ${CMAKE_CURRENT_SOURCE_DIR}/middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc
          ${CMAKE_CURRENT_SOURCE_DIR}/application/rtfw/Inc
          ${CMAKE_CURRENT_SOURCE_DIR}/../shared/inc
          ${CMAKE_CURRENT_SOURCE_DIR}/../3rdParty/include
        ${CMAKE_BINARY_DIR}/generated/
      )
      list(APPEND ${BL}_sources
          ${CMAKE_CURRENT_SOURCE_DIR}/../shared/src/stm32_startup.s
          ${CMAKE_CURRENT_SOURCE_DIR}/src/system_stm32f4xx.c
          ${CMAKE_CURRENT_SOURCE_DIR}/drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_cortex.c
          ${CMAKE_CURRENT_SOURCE_DIR}/drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash.c
          ${CMAKE_CURRENT_SOURCE_DIR}/drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash_ex.c
          ${CMAKE_CURRENT_SOURCE_DIR}/drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal.c
          ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
      )
      list(APPEND ${BL}_libDirs
      )
      list(APPEND ${BL}_libs
      )
      include_directories(${${BL}_inc})
      link_directories(${${BL}_libDirs})
      add_executable(${BL}
          ${${BL}_sources}
          ${TOOLCHAIN_EXTRA_OBJECTS}
      )
      target_compile_definitions(${BL} PRIVATE ${${BL}_DEFINES})
      target_link_libraries(${BL} PRIVATE ${${BL}_libs})
      set_target_properties(${BL} PROPERTIES
          LINK_FLAGS "${LINKER_FLAGS_BL}"
          OUTPUT_NAME "${BL}_${GIT_COMMIT_TAG}_${GIT_COMMIT_HASH}"
          VERSION "${${BL}_VERSION_MAJOR}.${${BL}_VERSION_MINOR}.${${BL}_VERSION_REVISION}"
          SOVERSION "${${BL}_SOVERSION}"
          SUFFIX ".elf"
      )
      
    2. Create bin/files form the ELF file via object copy

    3. Connect your programmer (p.e. ST-LINK)

    4. Open OpenOCD with a configuration like this

      echo "use the st link v2 cfg"
      source [find interface/stlink-v2.cfg]
      
      transport select hla_swd
      
      echo "set target to stm32f4x"
      
      source [find target/stm32f4x.cfg]
      
      reset_config srst_only
      
      $_TARGETNAME configure -event gdb-attach {
              echo "Debugger attaching: halting execution"
              reset halt
              gdb_breakpoint_override hard
      }
      
      $_TARGETNAME configure -event gdb-detach {
              echo "Debugger detaching: resuming execution"
              resume
      }
      
    5. Happy bug hunt