BACK
Microcontroller programming 101½: Minimalistic Toolchain

This article is STM32-specific. However, there may be common snippets of truth usable for other microcontrollers (mcus), too. Maybe it will evolve to a more generic text, sometime...

IDEs provide a simple and easy access to all components needed for programming mcus - editor, compiler, tools to download binary into the mcu and debugging. They have their shortcomings, too - usually they are a huge and complex bundle, the individual components don't necessarily fit together well, often they do "magic" for the user without making it clear, what exactly goes on, and without giving enough control to the user. In short, they "mostly work".

Some users prefer to give up the comfort promised by IDEs in exchange for a better control over things. Or they just want things to be lightweight, in the Unix spirit of tools. Luckily, some free and open-source tools provide just that (many of them originating in the Unix/Linux world), and there are also some such tools in the paid-for cathegory.

However, it's not simple to gather all the needed tools and get them going. There are very few guidelines for this style of work 1, and they inevitably tend to be of "I do it this way" style, rather than a general overview of all possible alternatives, and analysis of possible pitfalls and solutions. That would be quite hard and time-consuming (read: expensive) to do, and there's little incentive to do them. So, this text is just one of those "I do it this way" descriptions, too. What it tries to describe, though, is, what is possible with absolute minimum effort to start with STM32 programming, and some may be interested in that.

  1. Need compiler. This is easy, just get gcc.

    But this needs a bit of explanation - gcc is not one program, but a collection of programs (of which some are not even part of the gcc project itself - the assembler and linker used by gcc, and some optional post-processing utilities form the binutils project). They also can be compiled to run on various host platforms and they can be configured to generate binaries for various target platforms 2.

    And there's more - a gcc installation contains also gcc's internal libraries (for functionalities which are not worth to be compiled directly to instructions) and also the run-time libraries (for functions required by the C standard as "standard library functions", e.g. printf()). For the latter, in the Cortex-M world, usually, newlib is used (or its variant, newlib-nano).

    So, in spirit of this "minimal effort" text, you want to get such a binary bundle of gcc 3, which supports the Cortex-Mx cores. Most bundles called arm-none-eabi-gcc do so. They comes with all the stuff mentioned above packed up properly, so you don't have to care about all that. And, as a bonus, gdb (to be discussed about later) is thrown in, too.

    For Windows, the straighforward option is to go to ARM themselves 4 who maintain such bundles. There are Linux and Mac bundles there, too; but for Linux (including WSL) it may be simpler to use the native OS installer to get arm-none-eabi-gcc from given distro's repositories (I have no experience with Mac, sorry).

    Note, that some installers tend to place the directory where gcc is installed into the system PATH variable. If it does so, it may then be enough to type "arm-none-eabi-gcc(.exe)", or name of any other utility from the bundle, into command line, and the system runs the program; otherwise, if it's not in PATH, the complete path to program has to be typed. As usually, there's also a downside: if several gcc instances are installed in this way and all of them are pointed to in PATH, you may run the incorrect version inadvertently. So decide yourself 5.

  2. Things you need to add to your program to be able to compile it: CMSIS-mandated device headers, startup file, linker script 6.

    CMSIS is a moniker used by ARM for a mix of things, causing confusion. Originally, it meant: "We, ARM, told the individual chipmakers who licenced our cores, to provide device headers which describe registers of all internal peripherals of those devices such as GPIO or UART, in a uniform way. We also provided similar headers for those registers which are part of the processor core." It is not necessary to use these headers in programming, but most users do so 7.

    Startup file is a piece of program which is run as the first thing after reset and provides a "minimal OS-replacement" for the C program. As a bare minimum, it provides initialization of global and static variables, but in some cases it also sets up clocks, external memories; for C++ calls constructors, etc. And after that, it jumps to main(). Startup file also contains the interrupt vector table and stubs for the interrupts 8. Startup is usually written in assembler, although some "integrators" use carefully crafted startup in C.

    Linker stitches together all object files which are results of assembling what compiler compiled. Linker script is a file which tells the linker, how to do it.

    The easiest way to obtain all these for STM32 targets, is to get Cube for given STM32 family 9. There are three ways to get it: through CubeIDE; directly from ST's website (at st.com, search for e.g. "CubeF4", downloads the whole bundle but requires to have an account at st.com); or from ST's official github.com account (e.g. github.com/STMicroelectronics/STM32CubeF4 - substitute the last two letters for your STM32 family). The last option also allows to get only the needed files (using relative paths given below) rather than the whole bundle.

    The bulk of Cube bundle, which is several hundreds of megabytes large, zipped, contains examples based on Cube/HAL and Cube/LL, mostly repeated for all devboards (Nucleo, Disco, Eval) offered for that family. At this point, they are not needed; the essential files are (again, substitute 2-letter abbreviation of family, and the model number, as appropriate - using STM32F407 as an example):

    1. from [CubeF4]/Drivers/CMSIS/Device/ST/STM32F4xx/Include/, stm32f4xx.h and stm32f407xx.h

      The latter is the device header itself; the former allows to use it in a uniform way for the whole family, by using #include "stm32f4xx.h" in the code, while having defined the given model usually in command line (e.g. -DSTM32F407xx). stm32f4xx.h also defines couple of useful macros for manipulating bitfields within registers. 10

      The device header itself (stm32f407xx.h) also #includes the processor-core-specific header chain (see below).

      The device header also #includes system_stm32f4xx.h, which is part of the system how ST calls C functions from the startup code, intending to set up clocks, non-standard (e.g. external) memories etc. from startup, before main() is called. This is not in the spirit of minimal approach, see startup code description below. To avoid modifying the device header (i.e. to allow re-downloading it and using unmodified, should a newer version be used for any reason), while not being bothered by the C functions calls from startup code, the easiest approach is to create an entirely empty file (or it just containing comments) called system_stm32f4xx.h.

    2. from [CubeF4]/Drivers/CMSIS/Include/, all processor-specific headers needed for given STM32 model/family

      These headers are provided directly by ARM, and ARM likes to change their structure as they change CMSIS version, so there's no point to provide a definitive list. One way would be to get them all, but obviously, ARM (and ST) places files for *all* Cortex processor cores into this directory, so only a fraction of them is really needed.

      So, the easiest way to determine, which of them are needed, is to try to compile (or just preprocess) any simple source which already has #included the device-specific headers (see previous point), which ends with an error message, telling, which header files are missing. In our example for STM32F4xx, with CMSIS v4.30, core_cm4.h, core_cmInstr, core_cmFunc.h, core_cmSimd.h, and the gcc-specific cmsis_gcc.h were needed.

    3. from [CubeF4]/Drivers/CMSIS/Device/ST/STM32F4xx/Source/Templates/gcc, startup_stm32f407xx.s

      This is the startup file. It is not written in the spirit of minimal approach, where all that's needed from startup is the basic C variable initialization/zeroing and the very rudimentary processor setups, and everything else is passed to be controlled by user in its application. So, we are going to modify it to this approach by:

      • removing call to SystemInit() (by deleting or commenting out line containing bl SystemInit) - this is call to a C function which is supposed to set up clocks and/or memories;
      • removing call to __libc_init_array system function (again deleting or commenting out line containing bl __libc_init_array), this is not needed for minimal programs

      OTOH, for Cortex M4 and M7 cores ('F3/'F4/'F7/'L4/'G4/'H7), should floating-point operations ever be used, the floating-point coprocessor (FPU) has to be enabled in the startup code. In the original startup, this is done in the C function SystemInit(); if FPU is needed, it can be replaced by the following asm snippet:

          MOV.W   R0, #0xed00           // CPACR is located at address 0xE000ED88
          MOVT    R0, #0xe000
          LDR     R1, [R0, #0x88]       // Read CPACR
          ORR     R1, R1, #(0xF << 20)  // Set bits 20-23 to enable CP10 and CP11 coprocessors
          STR     R1, [R0, #0x88]       // Write back the modified value to the CPACR
          DSB                           // wait for store to complete
          ISB                           // reset pipeline, now the FPU is enabled
          
    4. linker script

      This one is tricky, as ST considers (to some extent, rightly so) the linker script to be part of the project/application, rather than a "library" item - the reason being, that its details may change as the application requires. However, we still can get a basic linker script from some of the example applications in Cube (preferrably some of the simplest ones), e.g. for our STM32F407 case, we could pick
      [CubeF4]/Projects/STM32F4-Discovery/Examples/GPIO/GPIO_EXTI/STM32CubeIDE/STM32F407VGTX_FLASH.ld

      As the devboards usually contain STM32 models with the largest amount of memory for given model/subfamily, it may be a good idea to modify the memories' sizes accordingly to the particular STM32 model we intend to use.

    You can put all the above files to a single directory together with the application source files; however, that is a bit messy approach. Another option is to put them to a common directory, but one different from the directory where the application sources reside; and provide to compiler path to headers using the -I command-line option, and explicit paths to the startup file and linker script. In yet another approach the directory structure of Cube can be mimicked (or the complete Cube bundle can be used), again, providing paths to all groups of headers and explicit paths to startup file and linker script. There are many similar approaches to arrangement of these files possible, and individual users may find different setups suitable for their particular needs.

  3. Compile

    The usual method for compilation involves a build system based on make or similar, but that's a topic for another article, and is not simple. Instead, we will use the fact, that gcc itself, rather than just being a compiler, instead, it's a "driver" program, which invokes all components of the translator (i.e. preprocessor+compiler+assembler+linker). So, for simpler programs, running gcc in a single command line is sufficient.

    To demonstrate compilation, we need some simple program - and what else would we use than the embedded "Hello World": the loopdelay blinky:

        // blinky for Disco 'F407 - 4 LEDs on PD12..PD15
    
        #include "stm32f4xx.h"
    
        // trivial loopdelay
        static void LoopDelay(volatile uint32_t n) {
        	while(n > 0) n--;
        }
        #define DELAY_CONSTANT 1000000  // roughly 0.5 sec at 16MHz HSI
    
        int main(void) {
          RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;  // enable GPIOD clock
          GPIOD->MODER |= (0b01 << (2 * 15));   // GPIO Out, blue LED on PD15
          while(1) {
            GPIOD->BSRR = 1 << 15;              // PD15 = 1
            LoopDelay(DELAY_CONSTANT);
            GPIOD->BSRR = 1 << (15 + 16);       // PD15 = 0
            LoopDelay(DELAY_CONSTANT);
          }
        }
    
    Let's put all the "system" files from previous section (device-specific headers stm32f4xx.h and stm32f407xx.h and the empty system_stm32f4xx.h, "doctored" startup file, and the linker script - let's omit ARM's processor-specific header files for now) to a directory (e.g. [ ]/work/), and let's create a subdirectory in that directory (e.g. [ ]/work/blinky/), in which we create a file blinky.c with the above content. In following, we assume that we have a command line (shell) run in [ ]/work/blinky/ 11; and assume we have installed gcc into /gcc/, so its binaries are in /gcc/bin/ (substitute for your real path, or omit if it's in system PATH, as needed). Simply type:
        /gcc/bin/arm-none-eabi-gcc blinky.c
    
    gcc will respond:
        blinky.c:3:27: fatal error: stm32f4xx.h: No such file or directory
    
    Of course, the header is not in the current file, so we need to give a path to headers, using gcc's -I command-line switch 12 (as we made the "project directory" subdirectory of the directory with headers, the path is simply the upper directory i.e. ..):
        /gcc/bin/arm-none-eabi-gcc -I.. blinky.c
    
    to which we get a relatively lengthy error, but as usually, only the first few lines are relevant:
        In file included from blinky.c:3:0:
            ../stm32f4xx.h:191:3: error: #error "Please select first the target STM32F4xx device used in your application (in stm32f4xx.h file)"
    
    which is because stm32f4xx requires to define a particular STM32 model, so we add it on the command line:
        /gcc/bin/arm-none-eabi-gcc -I.. -DSTM32F407xx blinky.c
    
        In file included from ../stm32f4xx.h:149:0, from blinky.c:3:
           ../stm32f407xx.h:183:81: fatal error: core_cm4.h: No such file or directory
    
    This is because we omitted the processor-specific headers, so now we can add them, one by one, as the compiler requires them after running it again and again, until all of them are added and the error message changes:
       /gcc/bin/../lib/gcc/arm-none-eabi/4.9.3/../../../../arm-none-eabi/lib\libc.a(lib_a-exit.o): In function `exit':
           exit.c:(.text.exit+0x2c): undefined reference to `_exit'
           collect2.exe: error: ld returned 1 exit status
    
    This is a somewhat confusing error, but the last line indicates, that this already is not a compiler error but a linker error. The reason for it is, that by default, it is expected that the user provides a couple of functions for so called semihosting, which we are not going to use now. This is again a topic for a separate article; for now, without further explanation, we add --specs=nosys.specs:
        /gcc/bin/arm-none-eabi-gcc -I.. -DSTM32F407xx --specs=nosys.specs blinky.c
    
    This time, gcc should finish working without any printout. Looking into our [ ]/work/blinky, we found there besides blinky.c a new file called a.out. What?
    This is a historical curiosity, the output "executable" binary file is called a.out unless gcc is commanded otherwise, which we will do promptly:
        /gcc/bin/arm-none-eabi-gcc -I.. -DSTM32F407xx --specs=nosys.specs blinky.c -o blinky.elf
    
    and this time compiler produces blinky.elf. However, this file wouldn't run in our microcontroller: the compiler by default compiles 32-bit ARM binary (while Cortex-Mx use the 16-bit Thumb instruction set); there is no startup code and we haven't used our startup file nor linker script either. So let's use -mthumb to command the compiler to use the 16-bit Thumb instruction set; -mcpu=cortex-m4 to use Cortex-M4-specific features; -Wl,-T../STM32F407VGTX_FLASH.ld to apply the linker script 13 and add the startup file as an additional source 14, 15:
        /gcc/bin/arm-none-eabi-gcc -I.. -DSTM32F407xx -mthumb -mcpu=cortex-m4 --specs=nosys.specs -Wl,-T../STM32F407VGTX_FLASH.ld ../startup_stm32f407xx.s blinky.c -o blinky.elf
    
    This should run without complaints 16, with a correct blinky.elf as the result.

    For convenience, we can put this line into a .bat-file/shell script.

  4. Post-Compile

    • Some tools for device programming don't accept .elf files, just .hex or .bin. The standard way to produce them is to use objcopy from binutils:

          /gcc/bin/arm-none-eabi-objcopy -O binary blinky.elf blinky.bin
          /gcc/bin/arm-none-eabi-objcopy -O ihex blinky.elf blinky.hex
      
      This can be added to the .bat/linker script after the line which performs the combination.

    • For debugging, sometimes it is a good idea to look at the disassembled result. Without further explanation at this point 17, the following lines (perhaps again stored in .bat/linker script) can be used:

          /gcc/bin/arm-none-eabi-objdump -h blinky.elf > blinky.lss
          /gcc/bin/arm-none-eabi-objdump -d -z -j .isr_vector blinky.elf >> blinky.lss
          /gcc/bin/arm-none-eabi-objdump -S blinky.elf >> blinky.lss
      

  5. Device programming (flashing) and debugging

    Now we are entering murky waters. Device programming 18 can be accomplished using several methods, using combination of hardware and software, all of which have their idiosyncracies. I personally prefer using gdb+openOCD, and in simple cases STLink+STLink Utility; and don't have personal experience with most of the other methods.

    Some of the options are:

    • STM32 built-in bootloader + CubeProgrammer

      All STM32 have a built-in bootloader, which allows to program the STM32 through some of UART, SPI, I2C, CAN, USB (DFU class) interfaces. To enter the bootloader, combination of dedicated pin(s) (BOOT0, BOOT1), detection of "FLASH empty", and several bits in the option bytes, are used, depending on (sub)family. For exact method of bootload entry for given particular (sub)family, interfaces available in the bootloader, and pins on which these interfaces are to be used, see AN2606.

      Protocols used on individual interfaces are described in separate ANs (AN2606 contains a list of them), for those who would like to write their own programming counterpart. For "normal" device programming from PC, the most likely used interfaces are UART (probably via some USB-UART converter) and USB/DFU, from CubeProgrammer.

      CubeProgrammer is ST's software for device programming, it's free to use although download requires login.

    • STLink + simulated mass storage

      The STLink hardware debuggin/programming tool comes in several flavours:

      • STLink/V1 - this is obsolete, unsupported. It's on the oldest Disco board (STM32VLDISCOVERY), which ST inexplicably still produces; don't buy it.
      • STLink/V2 - this is what you can buy as standalone debugger. It is also part of older Disco boards. It is widely cloned, but the clones may have problematic functionality.
      • STLink/V2-1 - an upgraded version of STLink V2, which besides the programming/debugging interface features also a virtual serial port (VCP) and an alternative programming method through simulated USB mass storage (disk). It is not available as standalone programmer, only as part of "mid-aged" Disco and Nucleo boards.
      • STLink-V3 - this comes in many flavours, both as standalone and as part of newer Disco and Nucleo boards. It's faster than STLink/V2 but should otherwise have the same basic functionality.

      STLink/V2-1 and STLink-V3, when powered (connected to USB) while being connected to target STM32, on the connected PC they enumerate so that a virtual mass-storage (disk) is created. Copying ("drag-and-drop") a .bin file onto this disk results in that file being programmed into the target. This is a convenient method for quick tests, but may be problematic on Linux.

    • STLink + CubeProgrammer or STLink Utility

      This is perhaps the "most official" method for device programming, using the STLink hardware tool described above.

      CubeProgrammer - which we've also described above - is the current programming software from ST, so it supports all STM32 including the newest ones. It has a "modern" interface, ignoring industry standard hierarchical menus, so it takes some time and reading the manual to find out how to use its features. It has also a command-line version which may be more convenient to use for those who prefer command-line operation and .bat/shell scripts.

      STLink Utility is an older version of ST's tools, which is no longer actively supported and does not support newer STM32 families (roughly from 'G0 on). It has a more conventional look than CubeProgrammer.

    • STLink + OpenOCD (+ gdb)

      Discussion of these tools is beyond the scope of this article.

    • BlackMagic Probe (+ gdb)

      This is primarily a debugging tool, so it is not really suited for basic programming. We won't discuss it here.

    • Segger J-Link + its native tools

      This is probably the leading commercial programming/debugging solution. Please refer to Segger's documentation/support.

    Use any of the above methods to program the binary into the target STM32. Some STM32 require a power cycle after the initial programming.

    The LED now should start blinking.

    At this point, debugging is limited to the "classic" methods such as blinking LEDs, toggling pins observed by oscilloscope or logic analyzer (LA), outputting printouts through UART or other interface, etc. On-chip debugging requires further software and will be discussed elsewhen and elsewhere (maybe).

  6. Better compilation

    While the program compiled using the method describe above works, it can be "better". Here are a few extra switches to consider:

    •  -O2 
      switches on middle-range optimizations (use -O1 for very mild optimizations, -O3 for more aggressive optimizations, -Os to optimize primarily for size, -Og to use only a mild optimization which, in gcc manual's words, "optimize debugging experience")
    •  -nostartfiles 
      removes unnecessary functions related to the gcc-default startup (which we don't use)
    •  -ffunction-sections -fdata-sections 
      creates separate sections for every function and every global variable; this allows the linker to remove unused functions and unused variables
    •  -Wl,-TSTM32F407VGTX_FLASH.ld,--gc-sections,-Map=blinky.map 
      this passes several options to the linker: -Txxx is the linker script; --gc-sections is the command to remove unused functions and variables (see above), and -Map generates a mapfile, a useful tool for debugging, which displays the location and size of individual functions and variables


Future/advanced topics may include:

Suggest. It's not very likely there will be enough time and energy to do these, but maybe.


Views expressed here are personal, arguable. YMMV. Things change, accomodate.
All trademarks belong to their respective holders, and other legalese blah blah blah.
Comments are welcome, please email them to stm32 at efton dot sk.


Thanks to brucehoult, tellurium and peter-h for comments.


1. One of the no-IDE guides here.

2. i.e. it's a cross-compiler. There are many possible combinations of "host" and "target" for gcc and its suite.

3. Here we are talking about getting "binary bundle" as opposed to getting sources and compiling gcc and its suite from scratch. Also note, that while the tools' names are given by their projects, e.g. "gcc", "ld", "objcopy" etc., the actual executable file names for them have "arm-none-eabi-" prefix added (arm as they are cross-compilers for ARM target; none means they compile for a no-OS target environment, i.e. "barebones"; eabi means that compilation follows the rules set by ARM EABI (Extended Application Binary Interface)).

4. ARM has reorganized its web in the past (also they used to use launchpad to host the gcc binaries), so if this link ceases to work, try the usual web search options.

5. I personally prefer only system programs to be run through PATH, and I don't consider any compiler to be a system program, so it's not in PATH and I type its whole path (usually not directly in command line, but through .bat/shell scripts/makefiles).

6. Users familiar with the avr-gcc environment may wonder: how comes that we never needed these things? Well, that's because startup files and linker scripts for all AVR families are included in the binary bundle, and gcc (the "driver" program, see section 3 above) is modified so, that from the AVR model entered by -mmcu switch. This can't be done with the ARM Cortex-M based microcontrollers, as there are many manufacturers using these processor cores with widely varying peripherals and interconnection fabric, requiring different device headers, startup files and linker scripts.

7. As with everything, there are drawbacks in using the CMSIS-mandated device headers, too. Some users prefer to have more control and to write things from scratch, relying only on information provided by manuals, thus avoiding potential errors made by ARM and/or ST in those headers (although, arguably, such errors are rare and the manufacturers are relatively eager to fix them timely). Others simply don't like the "style" of these headers, which may not match their usual programming style. Some users are simply not aware of their existence.
However, programs written without the CMSIS-mandated device headers are harder to read by the majority of programmers, who are accustomed to the symbols from those headers (even those who use Cube/HAL or Cube/LL mostly know those symbols, as Cube itself does use the CMSIS-mandated headers). As a consequence, such "individualistic" programmers are harder to cooperate with, and its harder to help them should they need external help or consultation.
In other words, the CMSIS-mandated headers provide a least-common denominator to mutually intelligibility and cooperability of STM32 programmers. The symbols naming also tends to match or closely follow the registers' and their bitfields' naming in the Reference Manual, so a fairly close match between what's written in program and what's written in the manual, can be achieved.
I personally do use the CMSIS-mandated device headers and do recommend to use them, for reasons mentioned; although I also am critical to the fact that ST omits to add symbols for individual "meanings" of non-single-bit bitfields, and that they are not entirely consistent in naming symbols across families.

8. These are short functions (or just one common function), usually containing just a return, marked "weak", so that an Interrupt Service Routine with the same name in user program can override it. The purpose here is to avoid linker complaining about their absence, as the vectors in the interrupt vector table have to point to existing labels (named functions) even if not all ISR are implemented in user program.

9. ST uses the name Cube as part of name for various software "products" related to STM32, e.g. CubeIDE, CubeMX, etc. Here we refer to the "firmware" bundle for individual families, officially named STM32Cube plus abbreviation of the family, e.g. for STM32F4xx it's STM32CubeF4.

10. Yes, we could've just used only the plain device header (i.e. in our case stm32f407xx.h); and would those macros prove to be useful, make our custom header out of just them.

11. Reason for this arrangement is, that this is the simplest way to quickly create projects by creating subdirectories, sharing the "common" files and having a "private" space for specifics of the individual projects. You can arrange things in other ways as you find fit.
Throughout this text, we assume basic working knowledge on operating systems, their shells and basic shell commands, and the path naming conventions (i.e. "../" meaning "upper directory", and the different conventions for the path separator, i.e. "\" in Windows and "/" in Linux).
Linux users are probably familiar with running shells and navigating in the directory system; Windows users can run cmd and navigate to given directory using cd within that. I use Total Commander in Windows, so that's really just a matter of typing "cmd" while being located in given directory.

12. gcc command-line switches are very extensive, here is their overview.

13. Syntax of this switch is somewhat convoluted - -Wl tells gcc (i.e. the "driver" program) to pass whatever is after -Wl (after converting commas - which are there to "hold" all the passed content together, i.e. to avoid parsing them as separate gcc switches - to spaces) as command-line options to the linker. So, -Wl,-T../STM32F407VGTX_FLASH.ld commands gcc to pass -T../STM32F407VGTX_FLASH.ld as a command-line switch to the linker, forcing the linker to use ../STM32F407VGTX_FLASH.ld as the linker script, instead of whatever is its default linker script (which is some generic script, certainly unsuitable for any STM32 or any other microcontroller).
List of linker command-line options is
here. A similar switch -Wa, can be used to pass command-line options to the assembler.

14. gcc as the "driver" program can accept multiple source files, it compiles/assembles all of them and then submits them to the linker, automatically, "behind the scenes". It also automatically differentiates between C, C++ and asm sources, based on the file's extension (unless this automatism is overriden by -x switch - this page contains also details on the particular languages and suffixes assigned to them). For historical reasons, files with suffix .s (for "source") are treated as asm sources; files with suffix .c are considered C sources.

15. As we are located in the "project subdirectory", and the linker script and startup file are in the "upper directory", that's why the "../". Also note, when running gcc and other programs in gcc suite, in parameters, which contain files and directories names and paths, forward slash / may and should be used even when running these tools under Windows - they internally convert it to proper path, while backslash is generally used as an escape character.

16. STM32F407VGTX_FLASH.ld as it is now, with older versions of linker will output two messages ("memory region `RAM' not declared" and "redeclaration of memory region `RAM'"). While these are tagged as "warning" rather than "error", in fact they result in generating a non-functioning binary with incorrect initialization value for the stack pointer. Reason is, that for inexplicable reasons, in that linker script, the RAM memory region is used in the expression defining _estack before it was defined by the MEMORY command. Newer versions of linker can cope with this; but with the older it is enough simply to move the _estack expression below the memories definition.

17. objdump and objcopy are part of the GNU binutils project. Here is objdump documentation, and here is objcopy documentation. Both programs also display help when they are run with --help command-line switch.

18. In English, there may be several expressions to describe this activity, neither of them perfect. "Programming" itself is ambiguous as it is used equally for "writing program" and "installing binary into the target's FLASH"; "device programming" is often used and appears to be the "official" terminus technicus of choice, but it is not much less ambiguous despite being lengthy and two-word. "Downloading" and "uploading" are both ambiguous and confusing as to the "directionality" to the action, and also don't describe the process of "putting data into FLASH" very well. "Flashing" is increasingly more popular, especially since program memory in modern microcontrollers is almost exclusively FLASH based, as opposed to EPROM in older mcus (whether Phase-Change Memory (PCM) being slowly introduced by ST, will have impact on this expression, remains to be seen). In some languages (including Slovak), a word translating to English as "burning" is used; however, this in English does not sounds that appropriate.

Created: 25.February 2024
Device programming footnote rephrased/"flashing" added; __libc_init_array() removal not justified by C++ anymore, link added: 31.March 2024 Typos fix: 6.April 2024