Skip to main content

1.3. Hello World #2 (the Mr Robot way)



In the previous tutorial we used Cube and HAL libraries to develop a blinking LED application. In this part, we'll see a radically different, ultimately minimalist approach, leading to (almost) the same result.
 

1. A word on the toolchain

The toolchain is the heart of the development process. It is a set of software tool that include an assembler, a C/C++ compiler, a linker, a debugger and some other utilities to build executable (i.e. flash-able) files.

Under the hood of STM32CubeIDE lies the GNU ARM Embedded Toolchain.

GNU toolchain, based on the gcc compiler and gdb debugger,  provides a free and open-source alternative to some market leading, and eventually expensive professional solutions such as:

ARM Keil µVisionIAR Embedded Workbench for ARM (EWARM)

Because the GNU ARM Toolchain can easily be integrated within Eclipse, quite a few free STM32 IDEs have been around in the lasts years based on this assembly:

  • SystemWorkbench for STM32 (AC6 SW4STM32) → still active, see www.openstm32.org

  • Atollic True Studio for STM32 → Deprecated, replaced by STM32CubeIDE.

  • Coocox → now dead.


It is even possible to integrate GNU ARM toolchain with other IDEs such as Visual Studio, Code:Blocks, and even the Arduino IDE... Just for you to know, there's no lack of alternatives here. Also, it is worth noting that ST is not the only one to provide a gcc/Eclipse based IDE for his MCUs range. For instance,  MCUXpresso is the NXP equivalent for Cortex-M based devices from the Kinetis familly. Getting familiar with gcc/Eclipse IDE is therefore a good investment.

A modern IDE provides several useful features such as an integrated code editor, build configuration management, buttons you can just click to fire complex actions such as the build process, comfortable debugging interface, navigation in large projects, code completion, symbol indexation...

Do we really need this comfort? Well Mr Robot sure doesn't.

Basically, a simple text editor, a toolchain, and a way to flash the executable image is all you really need to get some code running. 

Ready to try?

 

2. Prepare the toolchain

You can download and install the standalone GNU ARM Toolchain, but that is not necessary because it is already there if you previously installed STM32CubeIDE. All you have to do is to find it...

You'll find it in the STM32CubeIDE installation folder, under the plugins\ directory (your actual path may differ depending on your installation):

C:\STM\STM32CubeIDE\plugins\com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.10.3-2021.10.win32_1.0.100.202210260954\tools

Upcoming experiments require a console access to applications located in the \bin folder of the toolchain. To make things easier, you can add the path to that folder to your path environment variable. 

If you are under Windows operating system, use the search field to find your way to the environment variables dialog box. Then locate the Path variable within system variables and click Modify.

 

Then add a New path to the Path variable:

 

To void mistakes entering such a long path, I suggest copying the full path string using the property dialog of any file in the bin\ folder and then paste it as a new item in the Path environment variable list :

 

To test if the Path variable is correctly set, open a terminal application (under Windows, I suggest Windows PowerShell). Then enter the following command (use the tab key for auto-completion):

> arm-none-eabi-gcc.exe -v

 

Note that your gcc version may differ... You should get something similar to this:

 

3. Let's code

Create a new "blink\" folder under C:\STM.

Open a text editor (Notepad++ for instance, but any text editor will do). Start a new file, copy/paste the following code, and save it as main.c in the blink\ folder.

/*
 *  main.c
 *
 *  Created on: May 1, 2021
 *      Author: Laurent
 */
 
void    reset_handler 	(void);
void    default_handler	(void);
int     main 		(void);

/* Minimal vector table */
__attribute__ ((section(".isr_vector")))
void (* const interrupt_vector_table[])(void) =
{
	(void *)0x20000800, 	// 0 - stack
	reset_handler, 	 		// 1 - reset handler
	default_handler, 		// 2 - NMI handler
	default_handler, 		// 3 - Hardfault handler
};

/* Main program */
int main(void)
{
    int i;
    *(int *)0x40021014 |=  (0x01 <<17U);
    *(int *)0x48000000 &= ~(0xC00);
    *(int *)0x48000000 |=  (0x01 <<10U);
    while(1)
    {
    	*(int *)0x48000014 ^= 0x00000020U;
    	for (i=0; i<100000; i++);
    }
}

/* Reset handler */
void reset_handler (void)
{
    main();
}

/* Default handler */
void default_handler(void)
{
    while(1);
}

 

Still within the text editor, start a new file, copy/paste the following code, and save it as linkerscript.ld in the blink\ folder.

/*	Minimal LinkerScript */
/* Entry Point */
ENTRY(reset_handler)
/* Specify the memory areas */
MEMORY
{
FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 128K
RAM (xrw)      	: ORIGIN = 0x20000000, LENGTH = 16K
}
/* Define output sections */
SECTIONS
{
  /* The startup code goes first into FLASH */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) 	/* Startup code */
    . = ALIGN(4);
  } >FLASH
  /* The program code and other data goes into FLASH */
  .text :
  {
    . = ALIGN(4);
    *(.text)           		/* .text  sections (code) */
    *(.text*)          		/* .text* sections (code) */
    *(.glue_7)         		/* glue arm to thumb code */
    *(.glue_7t)        		/* glue thumb to arm code */
    *(.eh_frame)
    KEEP (*(.init))
    KEEP (*(.fini))
    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >FLASH
  /* Constant data goes into FLASH */
  .rodata :
  {
    . = ALIGN(4);
    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
    . = ALIGN(4);
  } >FLASH
 
  /* Initialized data sections goes into RAM, load LMA copy after code */
  .data : 
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */
    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */
  } >RAM AT> FLASH
  
  
  /* Uninitialized data section into "RAM" Ram type memory */
  . = ALIGN(4);
  .bss :
  {
    /* This is used by the startup in order to initialize the .bss section */
    _sbss = .;         /* define a global symbol at bss start */
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss*)
    *(COMMON)
    . = ALIGN(4);
    _ebss = .;         /* define a global symbol at bss end */
    __bss_end__ = _ebss;
  } >RAM
}

 

Your blink folder\ now contains 2 files:

4. Let's build

In the console, navigate (cd) to the blink\ folder, or just open a console from within the blink\ folder (Shift→Right-Click→Open PowerShell Window Here):

 

  • The first step is to compile the main.c source file. Enter the following command while watching the contents of the blink\ folder:

> arm-none-eabi-gcc.exe -I. -c -fno-common -O0 -g -mcpu=cortex-m0 -mthumb main.c

A new main.o file appears. This is an object file, resulting from the compilation of main.c source file.

  • The second step is to link object(s) file(s). Enter the following command while watching your blink\ folder:

> arm-none-eabi-ld.exe -T linkerscript.ld -o main.elf main.o

A new file main.elf appears. This file contains both executable code and debug information. It is not in a format that can be directly flashed into the MCU. A conversion is required.

  • The third step is to convert main.elf into a binary executable. Enter the following command while watching your blink\ folder:

> arm-none-eabi-objcopy.exe -Obinary main.elf main.bin

The executable file main.bin has been created.

Just for you to know, what you've just done are the 3 steps hidden behind the build button within Eclipse.

 

5. Prepare the MCU

For the demonstration to be complete, we need to make sure that your STM32 device is fully erased prior flashing main.bin.

This can be done using STM32CubeProgrammer.

Connect the Nucleo board to your computer, then launch STM32CubeProgrammer and open a connection.

You can erase the embedded flash using the button. Confirm the action:

Watch the Device memory tab. An erased flash reads as ones (0xFFFFFFFF in all 32-bit slots). Also note that flash starting address is 0x08000000.

Disconnect and close STM32CubeProgrammer. Make sure that no previous blinking program is running by pressing the Nucleo Reset button (nothing should happen).

 

6. Let's flash & run

In the console, now enter the following command:

> C:\STM\STM32CubeProgrammer\bin\STM32_Programmer_CLI.exe -c port=SWD -d .\main.bin 0x08000000

The path may differ depending on your installation. Note that the provided address corresponds to the beginning of the flash memory.

 

Now, press the Nucleo Reset button, and watch the LED!

 

7. Summary

This tutorial illustrates an ultimately lightweight approach to embedded programming. A simple text editor such as Notepad and the toolchain command line interface (CLI) are all we really need to write code and build executable. No absolute need for a sophisticated IDE.

Also, in comparison to the previous approach, you should wonder why the CubeHAL flow does involve so much source files. Only one, pretty short main.c with no library inclusion has been used here (in addition to the linker script that is always mandatory), and it works. We'll discuss that point in the next tutorial.

For now, I have to admit that I'm not Mr Robot, and I really enjoy the ergonomics of a modern IDE. So let's go back to Eclipse straight away!