STM32 Teil 1: Die Grundlagen

Sie können Code nicht vertrauen, den Sie nicht vollständig selbst geschrieben haben. - Ken Thompson
Vielleicht mein Lieblingszitat. Sie war der Grund, warum ich mich entschied, in die Tiefen des Kaninchenlochs einzutauchen. Ich habe meine Reise in die Welt des Programmierens erst vor kurzem begonnen, nur ungefähr ein Monat verging und ich beschloss, Artikel zu schreiben, um das Material zu konsolidieren. Alles begann mit einer einfachen Aufgabe, die Lampen in Ihrem Fotostudio mit Arduina zu synchronisieren. Das Problem wurde gelöst, aber ich ging nicht mehr ins Fotostudio, es ist keine Zeit. Von diesem Moment an beschloss ich, mich gründlich mit der Programmierung von Mikrocontrollern zu beschäftigen. Arduin, obwohl in seiner Einfachheit attraktiv, mochte ich die Plattform nicht. Die Wahl fiel auf die Firma ST und ihre beliebten Produkte. Zu diesem Zeitpunkt hatte ich noch keine Ahnung, was der Unterschied war, aber als typischer Verbraucher habe ich mir die Geschwindigkeit des „Prozessors“ und die Speichermenge verglichen und mir ein beeindruckendes Board mit einem STM32F746NG-Display gekauft - Discovery.Ich werde die Momente der Verzweiflung verpassen und auf den Punkt kommen.

Eingebettet in das Bild eines Programmierers las ich viel, studierte, experimentierte. Und wie ich oben bereits beschrieben habe, wollte ich gut lernen, das ist alles. Und dafür habe ich mir ein Ziel gesetzt, keine vorgefertigten Lösungen, nur meine. Und wenn alles für mich geklappt hat, werden Sie Erfolg haben.

Liste von allem, was Sie brauchen:

  1. Ubuntu 16+ virtuelle Maschine oder was auch immer
  2. Arm- Compiler - Download unter developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads
  3. openocd Debugger und Programmierer - Sie können nicht über den Link herunterladen, wir sammeln aus den Quellen, die Anleitung ist beigefügt

    git clone https://git.code.sf.net/p/openocd/code openocd
  4. Texteditor nach Ihren Wünschen

Nachdem alles installiert und montiert ist, können wir mit dem ersten Projekt fortfahren! Und nein, es ist nicht einmal eine blinkende Glühbirne. Zunächst müssen wir uns mit dem Initialisierungsprozess der Mikrotiterplatte selbst befassen.

Was wir brauchen:

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

Beginnen wir mit dem letzten Absatz Init.c. Zunächst sollte unser mk die Adresse laden. "Stapelzeiger" ist ein Zeiger auf die Speicheradresse, die für die PUSH- und POP-Anweisungen verwendet wird. Ich empfehle Ihnen dringend, diese beiden Anweisungen gründlich zu studieren, da ich nicht alle Anweisungen im Detail erläutern werde. Wie Sie dies implementieren, siehe unten.

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

Schauen wir uns nun dieses Beispiel an. Extern bedeutet, dass das Symbol extern ist, und wir haben dieses Symbol in der Datei Linker.ld deklariert. Wir werden etwas später darauf zurückkommen.

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

Hier verwenden wir ein Attribut, das den Compiler anweist, das Array im Abschnitt isr_vector zu platzieren, und dass es auch dann im Programm enthalten sein muss, wenn wir es nicht im Code verwenden. Und sein erstes Element wird der gleiche Zeiger sein.

Lassen Sie uns nun herausfinden, warum dieses Array ist und womit es gegessen wird. Im Gegensatz zu einem herkömmlichen Prozessor beginnt die Ausführung von Mikron auf der Armarchitektur, wenn nicht von der Nulladresse, sondern von der Adresse, auf die der Zeiger in diesem Array zeigt, alles kompliziert ist. Außerdem zeigt der erste Zeiger in diesem Array immer auf den Anfang des Stapels, der zweite zeigt jedoch bereits auf den Anfang unseres Codes.

Ich werde ein Beispiel geben. Es wird angegeben, dass der Stapel mit 0x20010000 beginnt und der Programmcode 0x0800008 lautet. dann kann das Array geschrieben werden als

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

Das heißt, die Steuerung initialisiert zuerst den Stapel, berücksichtigt dann die Adresse des ersten Befehls und lädt sie in das Programmzählerregister. Das Wichtigste ist nun, dass diese Zahlen je nach Modell unterschiedlich sein können, aber am Beispiel von stm32f7 kann ich mit Sicherheit sagen, dass sich dieses Array unter der Adresse 0x08000000 im Speicher befinden sollte. Von dieser Adresse aus beginnt mk nach dem Einschalten oder Zurücksetzen seine Arbeit.

Jetzt werden wir anhalten und darauf achten, wie dieses Array in den Abschnitt eingefügt wird, den wir benötigen. Dies erfolgt durch "ld" oder Linker. Dieses Programm sammelt unser gesamtes Programm zusammen und das Linker.ld-Skript wird dafür verwendet. Ich gebe ein Beispiel und analysiere es weiter.

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>
}

Mal sehen, was hier passiert. MEMORY definiert Speicherabschnitte und SECTIONS definiert Abschnitte. hier sehen wir unseren Stapel und die Tatsache, dass er gleich der Summe aus dem Beginn des Gedächtnisses und seiner Länge, dh dem Ende unseres Gedächtnisses, ist. Der Abschnitt .isr_vector, in den wir unser Array einfügen, ist ebenfalls definiert. >ROM_AXIMAm Ende unseres Abschnitts bedeutet, dass dieser Abschnitt in den Speicherabschnitt eingefügt werden sollte, der mit 0x08000000 beginnt, wie von unseren Mikrometern gefordert.

Wir platzieren das Array dort, wo es jetzt notwendig ist. Wir benötigen eine Anweisung, damit unser Mikron funktioniert. Hier ist die erweiterte 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);
}

Wie bereits erwähnt, muss die zweite Adresse ein Zeiger auf die erste Funktion oder Anweisung sein. Und wieder die Attribute, aber alles ist einfach, die Funktion gibt keine Werte zurück und folgt einem ABI am Eingang, dh nackt "nackt". In solchen Funktionen wird der übliche Assembler geschoben.

Jetzt ist es an der Zeit, unseren Code zu kompilieren und zu sehen, was sich unter der Haube befindet. Wir werden das Makefile vorerst nicht berühren.

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

Und hier sehen wir viel Unverständliches. in Ordnung:

  1. -mthumb kompiliert für armv7, das nur Thumb-Anweisungen verwendet
  2. -TLinker.ld gibt das Linker-Skript an. Standardmäßig wird es für die Ausführung in der Linux-Umgebung kompiliert
  3. -Wl, - gc-section -nostartfiles -nodefaultlibs -nostdlib entfernt alle Standardbibliotheken, Initialisierungsdateien der Umgebung C und alle anderen Hilfsbibliotheken wie Mathematik.

Natürlich macht es keinen Sinn, dies in Mikrometer zu laden. Aber es macht Sinn, die Binärdatei zu betrachten und zu studieren.

objcopy -O ihex prog.elf prog.bin

hexdump prog.bin

Und dann werden wir die Schlussfolgerung sehen. "00000000: 00 01 00 02 09 00 00 08", das unseren Erfolg symbolisiert. Dies ist mein erster Artikel, und ich konnte nicht das gesamte Material und die Essenz vollständig enthüllen. Im nächsten Abschnitt werde ich die Mechanismen detaillierter beschreiben und wir Freunde können zu einem Programm übergehen, das kein Licht blinkt, sondern die Uhr des Prozessors und der Busse konfiguriert.

All Articles