STM32 Partie 2: Initialisation

La programmation divise quelque chose de grand et d'impossible en quelque chose de petit et de très réel.


Bonjour à tous, pour commencer, je voudrais remercier les modérateurs d'avoir manqué mon premier post (dégoûtant), et dire bonjour à maman! Et je voudrais également remercier tous les lecteurs et les personnes qui ont souligné mes erreurs et aidé à les corriger. Je fais immédiatement une réserve qu'en russe je n'ai pas écrit de 6e année, en général, ne soyez pas en colère.

STM32 Partie 1: Les bases Commençons

donc. Dans un article précédent, j'ai rapidement passé en revue les tout premiers points. C'était notre fichier startup.c, qui était responsable de 2 vecteurs (pile, Reset_Handler) et un peu du script de l'éditeur de liens. Aujourd'hui, nous compléterons notre code d'initialisation, analyserons l'éditeur de liens pour les pièces de rechange et découvrirons comment tout fonctionne.


extern void *_estack;

void Reset_Handler();

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

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


Si ce code n'est clair pour personne, vous pouvez le lire ici (l'article n'est pas une fontaine, mais a essayé de l'expliquer).

Voyons maintenant ce qui manque ici et comment l'ajouter. De toute évidence, les microns ont d'autres vecteurs. Par exemple, le vecteur Hard_Fault est un vecteur qui est appelé à des actions incorrectes avec μ, par exemple, s'il essaie d'exécuter une instruction que le processeur ne comprend pas, il sautera à l'adresse du vecteur Hard_Fault. Il y a beaucoup de tels vecteurs et ce n'est pas un fait que dans notre programme, nous utiliserons tout. De plus, les vecteurs d'interruption sont dans la même table.

(Pour ceux qui ne savent toujours pas ce qu'est un vecteur, il s'agit d'un pointeur vers une adresse, alias «pointeur»).

Nous complétons notre code, puis je vous expliquerai ce que j'ai fait.

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


Ici, j'ai ajouté une nouvelle fonction Default_Handler (); qui gérera toutes les interruptions, absolument. Mais aussi avec l'aide, __attribute__((weak, alias("Default_Handler")));j'ai noté que si une fonction avec le même nom est déclarée, alors ce sera le gestionnaire de cette interruption. En d'autres termes, en ce moment, ce n'est qu'un surnom pour Default_Handler. Nous pouvons donc ajouter tous les vecteurs dans la liste de votre manuel de référence. Ceci est fait de sorte que si vous décidez soudainement d'utiliser une interruption, mais oubliez de créer un gestionnaire de fonctions, alors le Default_Handler habituel est appelé.

Je pense qu'à ce stade, avec les vecteurs, vous pouvez terminer et passer à l'initialisation des secteurs et à l'éditeur de liens. Pour commencer, mettons à jour notre startup.c en ajoutant l'initialisation des secteurs data et bss au corps Reset_Handler ainsi que plusieurs variables externes.

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


Ainsi, de nouvelles variables, et encore une fois, elles ont été déclarées dans notre script de l'éditeur de liens. Rappelez le script précédent . Et comparez-le également avec un nouveau.

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
}


Deux nouvelles sections ont été ajoutées, ce sont .data et .bss. Pourquoi avons-nous besoin d'eux? bien .data laissera des variables globales et statique ira à bss. Ces deux sections sont en RAM, et si tout est simple avec la section bss (ce ne sont que des zéros), alors il y a quelques difficultés avec la section data. Premièrement, les données sont déjà des données initialisées qui sont connues au moment de la compilation. Ainsi, initialement, la section de données se trouvera quelque part dans la mémoire flash. Deuxièmement, nous devons en quelque sorte indiquer à l'éditeur de liens le script qu'il est dans la mémoire flash, mais il sera transféré dans la RAM pendant l'exécution du programme. Jetons un coup d'œil à un morceau du script où nous décrivons la section des données.


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


Cela commence par la déclaration de _sidata et l'attribution, pour le moment, d'un sens qui ne nous est pas clair. Ensuite, une description de la section elle-même commence. Je vous conseille de lire à propos de la fonction ALIGN ici , mais en résumé, nous attribuons à notre point (adresse actuelle) une valeur qui est plus facilement acceptée par notre processeur lorsqu'il essaie de charger des données ou des instructions à partir de là.

Sont également inclus _sdata, _edata et la fin de la section. Mais à la fin de la section, il y a juste ce dont nous avons besoin.SRAM AT>ROM. Cette ligne indique à notre éditeur de liens que la section est en SRAM mais cette section est chargée dans la mémoire ROM ou flash. Revenons maintenant à notre variable _sidata et à la fonction LOADADDR. Cette fonction nous renverra une valeur qui sera égale à l'adresse de la section de chargement. Autrement dit, nous avons indiqué que la section était chargée dans la mémoire flash, mais nous l'utilisons dans la mémoire RAM, et pour transférer cette section, nous devons connaître son adresse dans la mémoire flash, ce que renvoie la fonction LOADADDR. Pendant ce temps, les variables _sdata et _edata indiquent l'emplacement de la section dans la RAM, nous donnant ainsi la possibilité de charger la section de données dans la bonne mémoire. Permettez-moi de vous rappeler que c'est ce que traite le code d'initialisation que nous avons complété ci-dessus.

Pourquoi avons-nous besoin de cet éditeur de liens et de telles difficultés avec la distribution des sections de notre application? La section Données n'est qu'un petit exemple des raisons pour lesquelles nous devons pouvoir placer et décaler des sections en mémoire. Voici un exemple de la section de texte que j'utilise.

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


L'essence de ce bloc est que je charge également la section de texte dans la mémoire flash, mais que j'utilise un bus différent pour accéder aux instructions et un accélérateur ART (un peu comme un cache normal). Et pour que tout fonctionne, j'ai indiqué l'adresse de chargement et l'adresse pendant l'exécution du code, mais cela ne fonctionnerait pas si je n'indiquais pas que la section devait être décalée. Il existe de nombreuses autres inventions de ce genre, je ne les montrerai pas toutes.

À ce stade, les sections sont chargées si nécessaire et initialisées. nous avons une base de travail pour l'écriture de code supplémentaire. Dans le prochain article, nous allons écrire notre premier pilote et faire clignoter une LED.

Merci à tous pour votre attention et la réussite de vos efforts.

All Articles