LED clignotante dans STM32 dans l'assembleur

Il y a quelque temps, je voulais apprendre l'assembleur, et après avoir lu la littérature pertinente, il était temps de pratiquer. En fait, il sera discuté plus loin. Au début, je pratiquais sur Arduino Uno (Atmega328p), maintenant j'ai décidé de passer à autre chose et j'ai adopté STM32. STM32F103C8 est en fait tombé entre mes mains et d'autres expériences auront lieu.

Les outils


J'ai utilisé les outils suivants:

  • Bloc-notes ++ - pour écrire du code
  • Compilateur GNU Assembler
  • STM32 ST-LINK Utility + ST-LINK V2 - pour flasher le code sur le microcontrôleur et déboguer

Commencer


Le principal objectif du langage d'assemblage pour moi est d'apprendre. Comme vous ne savez jamais où rencontrer un autre problème intéressant, il a été décidé de tout écrire à partir de zéro. La tâche principale était de comprendre le fonctionnement du vecteur d'interruption. Contrairement à Atmega dans STM32, le vecteur d'interruption ne contient pas d'instructions de saut:

jmp main 

Des adresses spécifiques y sont écrites, et lors de l'interruption, le processeur lui-même substitue l'adresse spécifiée dans le vecteur dans le registre PC. Voici un exemple de mon vecteur d'interruption:

 .org 0x00000000 SP: .word STACKINIT RESET: .word main NMI_HANDLER: .word nmi_fault HARD_FAULT: .word hard_fault MEMORY_FAULT: .word memory_fault BUS_FAULT: .word bus_fault USAGE_FAULT: .word usage_fault .org 0x000000B0 TIMER2_INTERRUPT: .word timer2_interupt + 1 

Je veux attirer l'attention du lecteur sur le fait que la première ligne n'est pas le vecteur de réinitialisation, mais les valeurs par lesquelles la pile sera initialisée. Immédiatement après, il y a un vecteur de réinitialisation suivi de 5 vecteurs d'interruption obligatoires (NMI_HANDLER - USAGE_FAULT).

Développement


La première chose avec laquelle j'ai été coincé a été la syntaxe de l'assembleur ARM. Même pendant l'étude du vecteur d'interruption, je suis tombé sur des références au fait que ARM a 2 types d'instructions Thumb et non Thumb. Et que Cortex-M3 (STM32F103C8 à savoir Cortex-M3) ne prend en charge qu'un ensemble d'instructions Thumb. J'ai écrit des instructions strictement selon la documentation, mais pour une raison quelconque, l'assembleur les a maudites.
registre non décalé requis
Il s'est avéré qu'au début du programme
.syntax unified
cela indique à l'assembleur que vous pouvez utiliser les instructions Thumb et non-Thumb en même temps.

La prochaine chose que j'ai rencontrée était les ports GPOI par défaut désactivés. Pour les faire fonctionner, vous devez entre autres définir les valeurs appropriées dans les registres RCC (réinitialisation et contrôle d'horloge). J'ai utilisé PORT C, il peut être activé en réglant le bit 4 (la numérotation des bits part de zéro) dans RCC_APB2ENR (registre d'activation d'horloge périphérique 2).

LED clignotante supplémentaire. Tout d'abord, comme dans Arduino, vous devez définir une broche pour l'enregistrement. Cela se fait via GPIOx_CRL (registre de contrôle bas) ou GPIOx_CRH (registre de contrôle haut). Ici, il est nécessaire d'annuler que pour chaque broche 4 bits sont responsables dans l'un de ces registres (registres 32 bits). 2 bits (MODEy) déterminent le débit de données maximum et la configuration des broches 2 bits (CNF). J'ai utilisé la broche 14 de PORT C, pour cela j'ai mis les bits [25:24] = 10 et les bits [27:26] = 00 dans le registre GPIOx_CRH.

Pour que la diode brûle, vous devez définir le bit correspondant dans GPIOx_ODR (registre de données de sortie). Dans mon cas, bit 14. Cela pourrait mettre fin à cet exemple simple en créant une fonction de retard et en mettant le tout dans une boucle, mais je ne pouvais pas le faire. J'ai décidé de régler des interruptions de minuterie ... Il s'est avéré que c'était en vain, principalement parce que les temporisateurs sont trop rapides pour ce genre de tâche.

Je ne décrirai pas en détail les paramètres de la minuterie, qui sont intéressés par le code sur Github . L'idée était simple, dans un cycle, envoyer le processeur en veille, quitter le ralenti par minuterie pour allumer / éteindre la LED et à nouveau en veille. Mais la minuterie a fonctionné beaucoup plus rapidement que j'ai réussi à faire tout ce qui précède, à cause de laquelle j'ai dû entrer dans un compteur supplémentaire.

Le compteur est une variable de 32 bits qui aurait dû être dans SRAM. Et puis un autre râteau m'attendait. Lorsque j'ai programmé dans Atmega pour placer une variable dans SRAM, via .org, j'ai défini l'adresse du début de la mémoire où le bloc de données a été réellement placé. Maintenant, après avoir lu un peu sur l'initialisation de la mémoire, je ne sais pas si c'était correct, mais cela a fonctionné. Et j'ai décidé de lancer la même chose avec STM32. L'adresse de début de mémoire dans STM32F103C8 est 0x20000000. Et quand j'ai fait .org à cette adresse, j'ai eu un binaire de 512 Mo. Cela m'a envoyé quelques nuits pour fumer des manuels. Je ne comprends toujours pas à 100% comment cela fonctionne, mais pour autant que je sache, la section .data place les valeurs par lesquelles les variables doivent être initialisées dans un fichier exécutable, mais au moment de l'exécution, le programmeur doit initialiser les valeurs des variables en mémoire. Corrigez-moi s'il vous plaît si je me trompe. J'ai fini par créer une variable comme celle-ci:

 .section .bss .offset 0x20000000 flash_counter: .word 

Initialisé au début de la fonction principale et la LED clignote. J'espère que cet article aide quelqu'un. Si vous avez des questions, je me ferai un plaisir d'y répondre.

Source: https://habr.com/ru/post/fr405959/


All Articles