ARM avec noyau Cortex Mx (en utilisant STM32F10x comme exemple)

Le microcontrôleur ARM Cortex M3 STM32F103c8t6 est largement utilisé comme microcontrôleur 32 bits pour les projets amateurs. Comme pour presque tous les microcontrôleurs, il existe un SDK, y compris, entre autres, des fichiers d'en-tête C ++ pour déterminer la périphérie du contrôleur.
Et là, le port série, par exemple, est défini comme une structure de données, et une instance de cette structure est située dans la zone d'adresse réservée aux registres et nous avons accès à cette zone via un pointeur vers une adresse spécifique.
Pour ceux qui ne l'ont pas rencontré auparavant, je vais décrire un peu comment il est défini, les mêmes lecteurs qui connaissent cela peuvent sauter cette description.
Cette structure et son instance sont décrites comme suit:
typedef struct { __IO uint32_t CR1; . . . __IO uint32_t ISR; } USART_TypeDef;
Vous pouvez voir plus de détails ici
stm32f103xb.h ≈ 800 kBEt si vous utilisez uniquement les définitions de ce fichier, vous devez écrire comme ceci (exemple d'utilisation du registre d'état du port série):
Et vous devez l'utiliser car les solutions propriétaires existantes connues sous le nom de CMSIS et HAL sont trop complexes pour être utilisées dans des projets amateurs.
Mais si vous écrivez en C ++, vous pouvez écrire comme ceci:
Une référence mutable est initialisée avec un pointeur. C'est un petit soulagement, mais agréable. Mieux encore, bien sûr, pour écrire une petite classe wrapper dessus, alors que cette technique est toujours utile.
Bien sûr, je voudrais écrire immédiatement cette classe wrapper sur le port série (EUSART - émetteur-récepteur asinhrone série universel étendu), si attrayant, avec des fonctionnalités avancées, un émetteur-récepteur asynchrone série et pouvoir connecter notre petit microcontrôleur à un ordinateur de bureau ou un ordinateur portable, mais des microcontrôleurs Les Cortex se distinguent par un système d'horloge avancé et vous devrez commencer par cela, puis configurer les broches d'E / S correspondantes pour fonctionner avec les périphériques, car dans la série STM32F1xx, comme dans pattes d'autres microcontrôleurs ARM Cortex ne peuvent pas configurer simplement les broches du port à l'entrée ou à la sortie et le travail en même temps avec la périphérie.
Eh bien, commençons par activer le timing. Le système d'horloge est appelé registres RCC pour la commande d'horloge et représente également une structure de données, un pointeur auquel est affectée une valeur d'adresse spécifique.
typedef struct { . . . } RCC_TypeDef;
Les champs de cette structure déclarés comme ceci, où __IO définit volatile:
__IO uint32_t CR;
correspondent aux registres de RCC, et les bits individuels de ces registres sont activés ou les fonctions d'horloge de la périphérie du microcontrôleur. Tout cela est bien décrit dans la
documentation (pdf) .
Un pointeur vers une structure est défini comme
#define RCC ((RCC_TypeDef *)RCC_BASE)
Travailler avec des bits de registre sans utiliser le SDK ressemble généralement à ceci:
Voici l'inclusion du port A.
Vous pouvez activer deux bits ou plus à la fois
Cela semble un peu inhabituel pour C ++, ou quelque chose d'inhabituel. Il serait préférable d'écrire différemment, comme ceci, par exemple, en utilisant la POO.
Cela semble mieux, mais au XXIe siècle, nous irons un peu plus loin, utiliserons le C ++ 17 et écrirons à l'aide de modèles avec un nombre variable de paramètres encore plus beau:
Où Rcc est défini comme ceci:
À partir de là, nous allons commencer à construire un wrapper sur les registres d'horloge. Tout d'abord, nous définissons une classe et un pointeur (lien) vers celle-ci.
Au début, je voulais écrire dans la norme C ++ 11/14 en décompressant récursivement les paramètres d'un modèle de fonction. Un bon article à ce sujet est fourni à la fin de l'article, dans la section des liens.
Considérez l'appel à la fonction d'activation de l'horloge du port:
Rcc.PortOn<GPort::A>();
GCC le déploiera dans un tel ensemble de commandes:
ldr r3, [pc, #376] ; (0x8000608 <main()+392>) ldr r0, [r3, #24] orr.w r0, r0, #4 str r0, [r3, #24]
Cela at-il fonctionné? Vérifiez ensuite
Rcc.PortOn<GPort::A, GPort::B, GPort::C>();
Hélas, le GCC pas si naïf a déployé l'appel de récursivité de fin séparément:
ldr r3, [pc, #380] ; (0x8000614 <main()+404>) ldr r0, [r3, #24] orr.w r0, r0, #4 ; APB2ENR |= GPort::A str r0, [r3, #24] ldr r0, [r3, #24] orr.w r0, r0, #28 ; APB2ENR |= Gport::B | GPort::C str r0, [r3, #24] #24]
Pour défendre GCC, je dois dire que ce n'est pas toujours le cas, mais seulement dans des cas plus complexes, ce qui sera visible lors de la mise en œuvre de la classe de port d'E / S. Eh bien, C ++ 17 est pressé de vous aider. Réécrivez la classe TRCC en utilisant les capacités de défilement intégrées.
Maintenant, il s'est avéré:
ldr r2, [pc, #372] ; (0x800060c <main()+396>) ldr r0, [r2, #24] orr.w r0, r0, #28 ; APB2ENR |= Gport::A | Gport::B | GPort::C str r0, [r3, #24]
Et le code de classe est devenu plus simple.
Conclusion: C ++ 17 nous permet d'utiliser les modèles avec un nombre variable de paramètres pour obtenir le même ensemble minimal d'instructions (même lorsque l'optimisation est désactivée) que celui obtenu lors de l'utilisation du travail classique avec le microcontrôleur via les définitions de registre, mais en même temps, nous obtenons tous les avantages d'un typage C ++ fort, vérifie lors de la compilation, réutilisé à travers la structure des classes de base du code, etc.
Voici quelque chose comme ça écrit en C ++
Rcc.PortOn<Port::A, Port::B, Port::C>();
Et le texte classique sur les registres:
RCC->APB2 |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN;
dépliez dans un ensemble optimal d'instructions. Voici le code généré par GCC (optimisation off -Og):
ldr r2, [pc, #372] ; (0x800060c <main()+396>) [ RCC] ldr r0, [r2, #0] ; r0 = RCC->APB2 // [ APB2] orr.w r0, r0, #160 ; r0 |= 0x10100000 str r0, [r2, #0] ; RCC->APB2 = r0
Vous devez maintenant continuer à travailler et à écrire une classe de port d'entrée / sortie. Travailler avec des bits de port d'E / S est compliqué par le fait que quatre bits sont alloués pour la configuration d'un tronçon de port et, par conséquent, 64 bits de configuration sont requis pour un port 16 bits, qui sont divisés en deux registres CRL et CRH 32 bits. De plus, la largeur du masque binaire devient supérieure à 1. Mais ici, le défilement à travers C ++ 17 montre ses capacités.

Ensuite, la classe TGPIO sera écrite, ainsi que des classes pour travailler avec d'autres périphériques, un port série, I2C, SPI, DAP, des temporisateurs et bien plus encore, qui sont généralement présents dans les microcontrôleurs ARM Cortex et il sera alors possible de clignoter avec de telles LED.
Mais plus à ce sujet dans la note suivante.
Sources du projet sur github .
Articles Internet utilisés pour rédiger des notes
Modèles avec un nombre variable d'arguments en C ++ 11 .
Innovations dans les modèles .
Innovation du langage C ++ 17. Partie 1. Convolution et dérivation .
Liste de liens vers la documentation des microcontrôleurs STM .
Macros de paramètres variablesArticles sur Khabr qui m'ont poussé à écrire cette note
Feu de circulation sur Attiny13 .
Julian Assange arrêté par la police britanniqueL'espace comme un vague souvenirÉcrit le 04/12/2019 - Happy Cosmonautics Day!
PS
Image STM32F103c8t6 de CubeMX.
Comme point de départ, le texte créé par l'extension Eclips pour travailler avec les
microcontrôleurs GNU MCU Eclipse ARM Embedded et
STM CubeMX est utilisé , c'est-à-dire qu'il existe des fichiers de fonctions standard C ++, _start () et _init (), les définitions des vecteurs d'interruption sont empruntées à Eclipse MCU ARM Embedded et le registre de base Cortex M3 et les fichiers de travail proviennent d'un projet réalisé par CubeMX.
PPSSur le débogage KDPV avec le contrôleur STM32F103c8t6 est représenté. Tout le monde n'a pas une telle carte, mais il n'est pas difficile de l'acheter, cependant, cela dépasse le cadre de cet article.