STM32 Part 1: The Basics

You cannot trust code that you did not write completely yourself. - Ken Thompson
Perhaps my favorite quote. It was she who became the reason why I decided to dive into the very depths of the rabbit hole. I started my journey into the world of programming quite recently, only about a month passed and I decided to write articles to consolidate the material. It all started with a simple task, to synchronize the lamps in your photo studio using Arduina. The problem was solved, but I did not go into the photo studio anymore, there is no time. From that moment, I decided to thoroughly engage in microcontroller programming. Arduin, although attractive in its simplicity, I did not like the platform. The choice fell on the company ST and their popular products. At that moment, I still had no idea what the difference was, but as a typical consumer I compared the speed of the “processor” and the amount of memory, I bought myself an impressive board with a STM32F746NG display - Discovery.I will miss the moments of despair and get right to the point.

Immersed in the image of a programmer, I read a lot, studied, experimented. And as I already described above, I wanted to study well, that's all. And for this I set a goal, not any ready-made solutions, only mine. And if everything worked out for me then you will succeed.

List of everything you need:

  1. Ubuntu 16+ virtual machine or whatever
  2. arm compiler - download at developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads
  3. openocd debugger and programmer - you won’t be able to download from the link, we collect from the sources, the instruction is attached

    git clone https://git.code.sf.net/p/openocd/code openocd
  4. Text editor to your liking

After everything is installed and assembled, we can proceed to the first project! And no, it's not even a blinking light bulb. To begin with, we must delve into the initialization process of the microwell itself.

What we need:

  1. Makefile
  2. Linker.ld
  3. Init.c

Let's start with the last paragraph Init.c. First of all, our mk should load the address "stack pointer" is a pointer to the memory address that is used for the PUSH and POP instructions. I strongly recommend that you thoroughly study these two instructions, as I will not explain in detail all the instructions. How to implement this, see below.

extern void *_estack;
void *vectors[] __attribute__((section(".isr_vector"), used)) = {
    &_estack
}

Now let's look at this example. Extern means that the symbol is external, and we declared this symbol in the Linker.ld file, we will return to it a bit later.

 __attribute__((section(".isr_vector"), used))

Here we use an attribute that tells the compiler to place the array in the isr_vector section and that even if we do not use it in the code, it still must be included in the program. And its first element will be that same pointer.

Now let's figure out why this array is and what it will be eaten with. Unlike a conventional processor, mic on the arm architecture starts execution when not from the zero address, but from the address pointed to by the pointer in this array, everything is complicated. Also, the first pointer in this array always points to the beginning of the stack, but the second already points to the beginning of our code.

I will give an example. it is given that the stack begins with 0x20010000 and the program code is 0x0800008. then the array can be written as

void *vectors[] __attribute__((section(".isr_vector"), used)) = {
    0x20010000,
    0x08000008
}

That is, the controller first initializes the stack, and then considers the address of the first instruction and loads it into the program counter register. Now the most important thing, depending on the model, these numbers may be different, but with the example of stm32f7 I can confidently say that this array should be in memory at the address 0x08000000. It is from this address that mk will start its work after turning on or reset.

Now we will stop and pay attention to how to put this array in the section we need. This is done by "ld" or linker. This program collects our entire program together and the Linker.ld script is used for this. I give an example and further analyze it.

MEMORY{
	ROM_AXIM (rx) : ORIGIN = 0x08000000, LENGTH = 1M
	RAM_DTCM (rwx): ORIGIN = 0x20000000, LENGTH = 64K
}

_estack = LENGTH(RAM_DTCM) + ORIGIN(RAM_DTCM);

SECTIONS{
	.isr_vector : {
	KEEP(*(.isr_vector))
	} >ROM_AXIM</code>
}

Let’s see what happens here. MEMORY defines sections of memory and SECTIONS defines sections. here we see our _estack and the fact that it is equal to the sum of the beginning of memory and its length, that is, the end of our memory. The .isr_vector section in which we put our array is also defined. >ROM_AXIMat the end of our section means that this section should be placed in the memory section that starts with 0x08000000 as required by our microns.

We put the array where it is necessary now we need some kind of instruction for our micron to work. Here is the augmented init.c:

extern void *_estack;

void Reset_Handler();

void *vectors[] __attribute__((section(".isr_vector"), used)) = {
    &_estack,
    &Reset_Handler
};

void __attribute__((naked, noreturn)) Reset_Handler()
{
    while(1);
}

As I mentioned earlier, the second address must be a pointer to the first function or instruction. And again, the attributes, but everything is simple, the function does not return any values ​​and follows any ABI at the entrance, that is, naked “naked”. In such functions, the usual assembler is shoved.

Now is the time to compile our code, and see what's under the hood. We will not touch the Makefile for now.

arm-none-eabi-gcc -c init.c -o init.o -mthumb

arm-none-eabi-gcc -TLinker.ld -o prog.elf init.o -Wl,--gc-sections -nostartfiles -nodefaultlibs -nostdlib

And here we see a lot of incomprehensible. in order:

  1. -mthumb compile for armv7, which uses only thumb instructions
  2. -TLinker.ld specify the linker script. by default, it compiles for execution in the Linux environment
  3. -Wl, - gc-sections -nostartfiles -nodefaultlibs -nostdlib remove all standard libraries, environment C initialization files, and all other auxiliary libraries such as mathematics.

Of course, there’s no sense in loading this into microns. But there is a sense in viewing and studying the binary.

objcopy -O ihex prog.elf prog.bin

hexdump prog.bin

And then we will see the conclusion. “00000000: 00 01 00 02 09 00 00 08” which will symbolize our success. This is my first article and I was not able to fully reveal all the material and essence, so in the next I will describe the mechanisms in more detail and we friends will be able to move on to a program that will not blink a light, but configure the clock of the processor and buses.

All Articles