STM32 Teil 2: Initialisierung

Das Programmieren zerlegt etwas Großes und Unmögliches in etwas Kleines und sehr Reales.


Hallo allerseits, zunächst möchte ich den Moderatoren dafür danken, dass sie meinen ersten (ekelhaften) Beitrag verpasst haben, und Mama Hallo sagen! Außerdem möchte ich mich bei allen Lesern und Menschen bedanken, die auf meine Fehler hingewiesen und geholfen haben, sie zu beheben. Ich mache sofort einen Vorbehalt, dass ich auf Russisch nicht ab der 6. Klasse geschrieben habe, im Allgemeinen nicht böse sein.

STM32 Teil 1: Die Grundlagen

Also fangen wir an. In einem früheren Artikel habe ich schnell die ersten Punkte durchgearbeitet. Dies war unsere Startup.c-Datei, die für 2 Vektoren (Stack, Reset_Handler) und ein wenig über das Linker-Skript verantwortlich war. Heute werden wir unseren Initialisierungscode ergänzen, den Linker für Ersatzteile zerlegen und herausfinden, wie alles funktioniert.


extern void *_estack;

void Reset_Handler();

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

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


Wenn dieser Code niemandem klar ist, können Sie ihn hier lesen (der Artikel ist kein Brunnen, aber versucht zu erklären).

Lassen Sie uns nun herausfinden, was hier fehlt und wie Sie es hinzufügen können. Offensichtlich haben Mikrometer andere Vektoren. Beispielsweise ist der Hard_Fault-Vektor ein Vektor, der bei nicht ordnungsgemäßen Aktionen mit μ aufgerufen wird. Wenn er beispielsweise versucht, eine Anweisung auszuführen, die der Prozessor nicht versteht, springt er zur Adresse des Hard_Fault-Vektors. Es gibt viele solcher Vektoren und es ist keine Tatsache, dass wir in unserem Programm alles verwenden werden. Interruptvektoren befinden sich ebenfalls in derselben Tabelle.

(Für diejenigen, die noch nicht wissen, was ein Vektor ist, ist dies ein Zeiger auf eine Adresse, auch bekannt als "Zeiger").

Wir ergänzen unseren Code und dann werde ich erklären, was ich getan habe.

extern void *_estack;

void Reset_Handler();
void Default_Handler();

void NMI_Handler()   __attribute__((weak, alias("Default_Handler")));
void HardFault_Handler()   __attribute__((weak, alias("Default_Handler")));
//     

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

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

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


Hier habe ich eine neue Funktion Default_Handler () hinzugefügt; das wird absolut alle Interrupts behandeln. Aber auch mit der Hilfe habe __attribute__((weak, alias("Default_Handler")));ich festgestellt, dass wenn eine Funktion mit demselben Namen deklariert wird, sie der Handler dieses Interrupts ist. Mit anderen Worten, im Moment ist es nur ein Spitzname für Default_Handler. So können wir alle Vektoren in der Liste aus Ihrem Referenzhandbuch hinzufügen. Dies geschieht so, dass, wenn Sie plötzlich beschlossen haben, einen Interrupt zu verwenden, aber vergessen haben, einen Funktionshandler zu erstellen, der übliche Default_Handler aufgerufen wird.

Ich denke an dieser Stelle können Sie mit Vektoren die Initialisierung von Sektoren und den Linker beenden. Lassen Sie uns zunächst unsere Datei startup.c erneut aktualisieren, indem Sie die Initialisierung der Daten- und BSS-Sektoren zum Reset_Handler-Body sowie zu mehreren externen Variablen hinzufügen.

extern void *_sidata, *_sdata, *_edata, *_sbss, *_ebss;

void __attribute__((naked, noreturn)) Reset_Handler()
{


	void **pSource, **pDest;
	for (pSource = &_sidata, pDest = &_sdata; pDest != &_edata; pSource++, pDest++)
		*pDest = *pSource;

	for (pDest = &_sbss; pDest != &_ebss; pDest++)
		*pDest = 0;

	while(1);
}


Also neue Variablen, und wieder wurden sie in unserem Linker-Skript deklariert. Rufen Sie das vorherige Skript auf . Und vergleichen Sie es auch mit einem neuen.

MEMORY{
	ROM(rx) : ORIGIN = 0x08000000, LENGTH = 1M
	SRAM (rwx):     ORIGIN = 0x20010000, LENGTH = 240K
}

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

SECTIONS{
	.isr_vector : {
	KEEP(*(.isr_vector))
	} >ROM
	
	.text : {
        . = ALIGN(4);
        *(.text)
    } >ROM
	
	_sidata = LOADADDR(.data);
        .data : {
		. = ALIGN(4);
		_sdata = .;
		*(.data)
		. = ALIGN(4);
		_edata = .;
	} >SRAM AT>ROM
	
	.bss : {
		. = ALIGN(4);
		_sbss = .;
		*(.bss)
		. = ALIGN(4);
		_ebss = .;
	} >SRAM
}


Es wurden zwei neue Abschnitte hinzugefügt, nämlich .data und .bss. Warum brauchen wir sie? Nun, .data hinterlässt globale Variablen und statische Daten gehen an bss. Diese beiden Abschnitte befinden sich im RAM, und wenn mit dem bss-Abschnitt alles einfach ist (dies sind nur Nullen), gibt es einige Schwierigkeiten mit dem Datenabschnitt. Erstens sind Daten bereits initialisierte Daten, die zur Kompilierungszeit bekannt sind. Somit liegt der Datenabschnitt zunächst irgendwo im Flash-Speicher. Zweitens müssen wir dem Linker irgendwie mitteilen, dass sich das Skript im Flash-Speicher befindet, aber es wird während der Programmausführung in den RAM übertragen. Schauen wir uns einen Teil des Skripts an, in dem wir den Datenabschnitt beschreiben.


        _sidata = LOADADDR(.data);
        .data : {
		. = ALIGN(4);
		_sdata = .;
		*(.data)
		. = ALIGN(4);
		_edata = .;
	} >SRAM AT>ROM


Es beginnt mit der Erklärung von _sidata und der momentanen Zuordnung einer Bedeutung, die uns nicht klar ist. Als nächstes beginnt eine Beschreibung des Abschnitts selbst. Ich rate Ihnen , über die ALIGN - Funktion lesen hier , aber auf den Punkt gebracht, weisen wir unseren Punkt (aktuelle Adresse) ein Wert, der leichter von unserem Prozessor akzeptiert wird , wenn es versucht , Daten oder Anweisungen von dort zu laden.

Ebenfalls enthalten sind _sdata, _edata und das Ende des Abschnitts. Aber am Ende des Abschnitts gibt es genau das, was wir brauchen.SRAM AT>ROM. Diese Zeile teilt unserem Linker mit, dass sich der Abschnitt im SRAM befindet, dieser Abschnitt jedoch in den ROM- oder Flash-Speicher geladen ist. Nun zurück zu unserer _sidata-Variablen und der LOADADDR-Funktion. Diese Funktion gibt uns einen Wert zurück, der der Adresse des Ladeabschnitts entspricht. Das heißt, wir haben angegeben, dass der Abschnitt in den Flash-Speicher geladen wurde, aber wir verwenden ihn im RAM. Um diesen Abschnitt zu übertragen, müssen wir seine Adresse im Flash-Speicher kennen, was die LOADADDR-Funktion zurückgibt. Währenddessen geben die Variablen _sdata und _edata die Position des Abschnitts im RAM an, wodurch wir die Möglichkeit haben, den Datenabschnitt in den richtigen Speicher zu laden. Ich möchte Sie daran erinnern, dass dies der Initialisierungscode ist, den wir oben ergänzt haben.

Warum brauchen wir diesen Linker und solche Schwierigkeiten bei der Verteilung von Abschnitten unserer Anwendung? Der Datenabschnitt ist nur ein kleines Beispiel dafür, warum wir Abschnitte im Speicher platzieren und verschieben müssen. Unten ist ein Beispiel für den Textabschnitt, den ich verwende.

.text (ORIGIN(ROM_ITCM) + SIZEOF(.isr_vector)): {
        . = ALIGN(4);
        *(.text)
    } AT>ROM_AXIM


Das Wesentliche an diesem Block ist, dass ich den Textabschnitt auch in den Flash-Speicher lade, aber einen anderen Bus verwende, um auf Anweisungen und einen ART-Beschleuniger zuzugreifen (ähnlich einem normalen Cache). Und damit alles funktioniert, habe ich die Ladeadresse und die Adresse während der Codeausführung angegeben, aber es würde nicht funktionieren, wenn ich nicht angeben würde, dass der Abschnitt versetzt werden soll. Es gibt noch viele solche Erfindungen, ich werde sie nicht alle zeigen.

In dieser Phase werden Abschnitte bei Bedarf geladen und initialisiert. Wir haben eine Arbeitsbasis für das weitere Schreiben von Code. Im nächsten Artikel werden wir unseren ersten Treiber schreiben und eine LED blinken lassen.

Vielen Dank für Ihre Aufmerksamkeit und Ihren Erfolg bei Ihren Bemühungen.

All Articles