Une brève introduction au développement d'applications de microcontrôleur stm32

Très souvent, les gens se tournent vers moi pour leur demander de les aider à démarrer avec les microcontrôleurs de la famille stm32. En répondant à leurs questions et en les aidant dans leurs projets, j'ai réalisé qu'il valait mieux écrire un article qui serait utile à tous ceux qui veulent commencer à programmer des microcontrôleurs stm32. Malgré toutes ses nombreuses fonctionnalités, les contrôleurs stm32 ont un seuil d'entrée assez élevé, qui pour beaucoup reste indisponible. Dans cet article, je vais essayer de donner au lecteur un guide détaillé sur la façon de créer des projets sur stm32 et d'organiser votre programme.

En utilisant le microcontrôleur stm32f103c8t6 et le module Blue pill comme exemple, nous examinerons la structure d'un projet typique pour l'environnement de développement IAR et créerons un firmware fonctionnel.

Tous ceux qui sont intéressés à commencer à travailler avec stm32, bienvenue sur cat.

De quoi avons-nous besoin pour poursuivre nos travaux


Tous les exemples suivants ont été réalisés dans l'environnement de travail IAR Embedded pour ARM v7.30. L'environnement est installé sur Windows XP, qui est installé dans la machine virtuelle VirtualBOX lancée à partir du capitaine Mac OS X El. Le programmateur ST-LINK est également utilisé, qui se connecte à la carte de pilule bleue achetée sur AliExpress pour ~ 120₽.

Pour créer un projet typique, vous aurez besoin de:

  • Module Blue Pill ou similaire

    Ils sont facilement localisés sur AliExpress avec la demande «carte stm32f103c8t6» et coûtent environ 100 roubles.

  • Programmeur ST-Link

    Il est également facilement disponible sur AliExpress sur demande de "stlink v2" et coûte également environ 100 roubles.


    Il existe une version plus complète, conçue pour connecter un connecteur ruban standard IDC20

  • Environnement de développement IAR 7.30 ou version ultérieure

    La version limitée 8.10 peut être téléchargée sur le site officiel.
  • Un modèle de projet qui contient tous les composants nécessaires

    Le modèle peut être téléchargé ici.
  • Référence de la fonction STDPeripheralLibrary3.5.0

    Le projet comprend une bibliothèque de fonctions StdPeriph3.5.0. Un peu vieux pour les projets sérieux, mais pour les débutants c'est assez simple et évite un mal de tête en travaillant avec la périphérie du microcontrôleur. Le manuel au format WinHelp (CHM) peut être téléchargé
    D'ici
  • RM0008 Manuel de référence

    Manuel de référence pour les microcontrôleurs de la famille stm32f103c8. Le livre de référence contient une description du cœur et de la périphérie du microcontrôleur, son architecture et des descriptions des registres. Il est conseillé, au fil du temps, de lire et de traverser et de savoir comment fonctionne chaque composant périphérique. Si vous êtes un partisan du CMSIS nu, alors sans ce PDF, vous ne pouvez pas le faire. Si le débutant qui utilise Perlib, vous devez toujours lire comment fonctionne tel ou tel module périphérique. Vous pouvez télécharger la référence sur le site Web de STMicroelectonics

Modèle de projet


Le modèle de projet ci-joint n'a pas été entièrement extrait de quelque part sur Internet, mais a été créé de nos propres mains à partir d'exemples qui sont allés à PeripheralLibrary, de fichiers du package CMSIS et de nos propres améliorations. Le modèle de projet ne contient pas de fichiers qui appartiennent à n'importe quel environnement de développement et peuvent être utilisés pour chacun d'entre eux (je ne l'ai pas testé, mais je suppose que oui).

Une description détaillée de la structure du répertoire peut sembler trop compliquée pour les débutants, cependant, pour l'étudier, elle sera également utile.

Structure du répertoire du projet
  • config
    stm32f10x_conf.h

    Ce fichier appartient au package StdPeripheral et contient l'inclusion de fichiers d'en-tête utilisés dans le projet périphérique du microcontrôleur. Il est supposé que le programmeur commentera les fichiers inutilisés, selon le projet. Par défaut, tous les fichiers sont inclus.
  • noyau

    Ce répertoire contient des fichiers CMSIS spécifiques au cœur du processeur ARM CortexM3.
  • perlib

    Ce répertoire contient les fichiers d'en-tête et le code source de la bibliothèque Perlib dans les répertoires inc et src .
  • démarrage

    Voici les fichiers avec le code d'initialisation principal du contrôleur, qui installent les gestionnaires d'interruption du noyau ARM et appellent les fonctions d'initialisation du système d'horloge du noyau et l'initialisation PLL. Chaque type de microcontrôleur possède son propre fichier distinct.

    Le code de ce fichier est exécuté AVANT l'appel de la fonction main () de votre programme.
    Les gestionnaires d'interruption du noyau ARM, ainsi que les fonctions d'initialisation de l'horloge, ne sont pas stockés dans ces fichiers, ils sont uniquement appelés. Et ils sont stockés dans les fichiers du répertoire système, qui seront discutés plus tard.

    Par exemple, pour que le microcontrôleur stm32f103c8t6 fonctionne à 72 MHz et non à 8 MHz par défaut, vous devez connecter le fichier startup_stm32f10x_md.s dans le projet.
  • système

    Ce répertoire contient des fichiers qui contiennent toutes les fonctions de l'initialisation initiale du système, des gestionnaires d'interruption pour le noyau ARM, ainsi que des descriptions des noms et des champs des registres du microcontrôleur.

    stm32f10x.h

    Il s'agit d'un fichier du package CMSIS qui contient les adresses, les noms des registres et leurs champs pour la périphérie du microcontrôleur. Chaque module périphérique est présenté ici comme une structure dont les champs sont des registres. Il définit également des constantes et des masques de bits pour les champs de registre.

    stm32f10x_it

    Les gestionnaires d'interruption du noyau ARM sont écrits ici. À l'exception du gestionnaire SysTick, que j'ai utilisé pour calculer les intervalles de temps, ces gestionnaires d'interruption sont vides. Si, soudainement, votre projet nécessite la gestion des interruptions du noyau, alors ce fichier est fait pour vous.

    Ne confondez pas la gestion des interruptions du cœur du processeur et la gestion des interruptions périphériques. Les interruptions périphériques, telles qu'une minuterie ou USART, doivent être effectuées dans vos propres fichiers de projet, que vous écrivez vous-même et ajoutez à la racine du répertoire du projet.

    system_stm32f10x

    Ces deux fichiers contiennent les mêmes fonctions d'initialisation de l'horloge du noyau et de la PLL qui sont appelées à partir du fichier d'initialisation dans le répertoire de démarrage. À savoir, les fonctions SystemInit et SystemCoreClockUpdate.


Créer un projet


Pour créer notre premier projet, vous devez d'abord créer un dossier pour celui-ci. Appelons cela EX01.
Nous lançons IAR et dans le menu Projet , sélectionnez Créer un nouveau projet .

Figure 1. Création d'un nouveau projet


La valeur de la chaîne d'outils doit être laissée à ARM, et nous sélectionnons le modèle de projet C → principal. Cliquez ensuite sur OK.

Figure 2. Enregistrement d'un projet vide


Une fenêtre de sauvegarde du projet apparaîtra. Dans ce document, vous devez sélectionner le dossier créé précédemment. Appelons le projet ex01. Maintenant, vous pouvez cliquer sur Enregistrer .

En conséquence, un fichier principal vide et un projet vide seront créés, qui ne contiennent pas encore de paramètres. L'étape suivante consiste à enregistrer l'espace de travail afin qu'il n'interfère pas avec nos questions à l'avenir. Cliquez sur Fichier → Enregistrer l'espace de travail . Appelez-le ex01 et cliquez sur Enregistrer .

Figure 3. Enregistrement de l'espace de travail


Nous sommes maintenant prêts à utiliser le modèle de projet. Copiez-le de l'archive dans notre dossier EX01.

Figure 4. Copie de fichiers d'un modèle vers un nouveau projet


Les fichiers principaux qui ont créé le modèle de projet doivent être remplacés par des fichiers du projet.

Après avoir copié le modèle de projet, vous devez configurer le projet lui-même.

Pour ce faire, sélectionnez le menu Projet → Options . Une fenêtre s'ouvrira qui contiendra une liste de catégories d'options sur la gauche, chacune ayant certains signets.

Sélectionnez la catégorie Options générales et l'onglet Cible . Dans le groupe Variante du processeur , sélectionnez l'option Périphérique et cliquez sur le bouton pour sélectionner un périphérique spécifique à côté de lui. Dans notre cas, ce sera ST → STM32F103 → ST STM32F103x8.

Figure 5. Sélection du périphérique cible pour le projet


La catégorie suivante qui requiert notre attention est la catégorie du compilateur C / C ++ et l'onglet Préprocesseur .

Figure 6. Paramètres des signets du préprocesseur


Le bloc Additionner les répertoires doit être rempli avec des liens vers les répertoires du modèle de projet.

PreInclude file, vous devez sélectionner le fichier de configuration de la bibliothèque PerLib et les symboles définis doivent être spécifiés avec STM32F10X_MD afin que les fichiers d'initialisation établissent l'horloge du noyau correcte et configurent correctement la PLL.

Puisque nous utilisons ST-Link v2 en tant que programmeur, vous devez choisir le pilote que l'environnement de développeur utilisera. Sélectionnez la catégorie Débogage et l'onglet Configuration , sur lesquels sélectionnez Driver ST-Link dans la liste déroulante.

Figure 7. Choix d'un outil de débogage


Vous devez maintenant configurer le téléchargement du firmware sur le contrôleur. Vous pouvez le faire dans la même catégorie, sur l'onglet Téléchargement . Nous sommes intéressés par les options Vérifier le téléchargement et Utiliser le (s) chargeur (s) flash .

Figure 8. Configuration des paramètres de téléchargement du micrologiciel dans le microcontrôleur


Puisque nous avons choisi ST-Link comme moyen de télécharger le firmware sur le contrôleur et de déboguer, vous devez configurer son pilote pour qu'il fonctionne avec notre contrôleur. La carte Blue Pill n'a pas de connecteur JTAG à part entière qui pourrait fonctionner en utilisant le protocole JTAG complet. À la place, nous utiliserons son mode simplifié, appelé SWD. Ce mode JTAG utilise seulement trois lignes. Ce sont GND, SWDCLK et SWDIO. Par défaut, le mode JTAG complet est activé, nous devons donc le changer en SWD et définir la fréquence de base sur 72 MHz.
Dans la liste des catégories, sélectionnez ST-LINK et changez l'option dans le groupe Interface en SWD.

Vous pouvez maintenant cliquer sur OK , notre projet est configuré.

Figure 9. Configuration du pilote ST-LINK


Il reste maintenant à faire la dernière, mais très importante action, avant de commencer à compiler notre premier programme. Vous devez ajouter des fichiers du modèle au projet.

Les fichiers sont ajoutés au projet dans le panneau Espace de travail. Il y a déjà un fichier principal, cependant nous devons ajouter PerLib, ainsi qu'un des fichiers d'initialisation du répertoire de démarrage et les fichiers d'initialisation du répertoire système. Nous pourrions simplement jeter un tas, mais alors nous aurions à souffrir et si le projet devenait trop grand, alors un tel vidage serait très gênant.

L'environnement de développement IAR vous permet de créer des groupes de fichiers. Les groupes sont un concept purement virtuel. Ils vous permettent uniquement d'organiser des fichiers dans un projet. Les groupes n'ont rien à voir avec les répertoires de disques.

Nous allons créer des groupes pour chaque répertoire à partir du modèle de projet et y placer les fichiers de modèle.

Commencez par créer le groupe de configuration et placez-y le fichier stm32f10x_conf.h du répertoire de configuration.
Pour ce faire, cliquez avec le bouton droit sur le nom du projet dans la fenêtre Espace de travail et sélectionnez Ajouter → Ajouter un groupe dans le menu déroulant. Nommez le groupe Config.

Pour ajouter des fichiers à ce groupe, faites un clic droit dessus et sélectionnez Ajouter → Ajouter des fichiers. Dans la fenêtre qui s'ouvre, ouvrez le dossier config et sélectionnez le fichier stm32f10x_conf.h.



De même, nous ajoutons le contenu des dossiers Perlib, Démarrage et Système. Vous n'avez pas besoin d'ajouter le dossier Core, seuls les fichiers d'en-tête sont disponibles à partir de la bibliothèque Perlib ajoutée.

Figure 11. Vue complète d'un projet vide et entièrement configuré


Maintenant, le projet est complètement prêt pour un développement ultérieur.

Un peu de théorie


De nombreux programmeurs débutants sont habitués au fait que le programme se compose d'un cycle dans lequel les fonctions sont appelées les unes après les autres. La fonction suivante n'est appelée qu'après que la précédente a complètement terminé son travail. Ce paradigme est proposé par Arduino ou divers articles avec des leçons pour débutants. Cependant, les grands projets sont rarement monothread. En règle générale, un firmware plus ou moins sérieux peut avoir plusieurs threads.

Les microcontrôleurs, pour organiser le multithreading, utilisent des systèmes d'exploitation en temps réel (RTOS), tels que ThreadX ou FreeRTOS. Tous vous permettent de créer de nombreux cycles de ce type dans lesquels les fonctions sont exécutées les unes après les autres, seuls les cycles fonctionnent en même temps. Comme plusieurs Arduins enfoncés dans un microcontrôleur.

Malgré toute sa puissance, les RTOS introduisent certaines difficultés. Par exemple, chaque thread a sa propre pile, sa propre zone de mémoire. Si plusieurs threads doivent accéder au même emplacement mémoire, ils doivent synchroniser leurs actions à l'aide de mutex ou de sémaphores. Une mauvaise utilisation des objets de synchronisation peut entraîner des blocages ou une inversion des priorités des threads. De plus, le traitement des interruptions de la périphérie nécessite également une attention particulière dans un environnement multi-thread, car le problème se pose de sauvegarder la pile et de choisir les conditions dans lesquelles l'appel d'interruption ne détruit pas la pile de flux interrompue. Et le gestionnaire d'interruption lui-même doit également fonctionner jusqu'à la fin.

Le RTOS alloue un petit intervalle de temps à chaque thread. Après cet intervalle, le RTOS passe au flux suivant (peu importe si le précédent a terminé ses actions ou non) et ainsi de suite. Différents flux peuvent recevoir des intervalles de temps différents, selon leur priorité. Ce type de multithreading est appelé éviction.

Pour trier les flux et leur transférer le contrôle pendant un court intervalle de temps, le composant RTOS, appelé «ordonnanceur», est responsable.

Il est difficile pour les programmeurs débutants de maîtriser immédiatement les périphériques énormes et complexes de stm32 tout en apprenant le RTOS.

Heureusement, il existe des moyens de créer des applications multithreads sans aucun RTOS. Pour cela, le «multithreading coopératif» vient à notre secours. Le multithreading coopératif vous permet de réaliser des projets multithread relativement petits sans la participation de RTOS.

Quelle est l'essence du multithreading coopératif? Avec un tel multithreading, chaque thread prend autant de temps processeur qu'il en a besoin, mais pas assez pour terminer sa tâche en une seule fois. Cela met en avant des exigences très strictes pour le style d'écriture des applications coopératives multi-thread.

Le multithreading coopératif présente plusieurs avantages et inconvénients. Le choix du paradigme multithreading dépend entièrement du développeur et des exigences de la tâche qu'il effectue.
Les principaux avantages du multithreading coopératif sont l'absence d'un planificateur, d'une pile unique pour tous les threads, l'absence de la nécessité de synchroniser les threads et la simplicité du traitement des interruptions depuis la périphérie.

Malheureusement, il existe également des inconvénients. En particulier, le gel de l'un des threads entraînera le gel de l'ensemble du programme dans son ensemble. En outre, l'orthographe incorrecte d'un ou de plusieurs threads peut entraîner un retard dans l'exécution du reste. Et ce n'est pas une liste complète.

Structure d'application coopérative multi-thread


Les applications coopératives multi-thread sont basées sur une machine d'état. Je ne le décrirai pas en détail, car ici il est déjà décrit en détail. Cependant, je vais expliquer brièvement l'essentiel. Une machine à états est un objet abstrait dont le nombre d'états est fini. Un objet passe d'un état à un autre soit sous l'influence de facteurs externes, soit à cause de processus internes. Dans notre cas, un thread d'application coopérative est une implémentation d'une machine d'état.

Un flux a une liste d'états. Dans chaque état, soit une action courte soit rien n'est effectuée. La commutation d'états peut être effectuée soit en appelant des fonctions externes, soit lorsque surviennent des conditions dans lesquelles il n'est plus possible que le flux soit dans l'état actuel et qu'il soit nécessaire de passer à un autre état.

Depuis plusieurs années de travail avec les microcontrôleurs stm32, j'ai développé une structure d'application coopérative multi-thread, que je souhaite vous présenter.

Chaque flux est un module distinct (fichier d'en-tête et fichier de code).

Le module a des fonctions publiques, dont les prototypes sont enregistrés dans le fichier d'en-tête et privés, dont l'appel est impossible de l'extérieur. Chaque module a au moins deux fonctions publiques:

void XXX_Init(); void XXX_Control(); 

La fonction XXX_Init () est appelée avant la boucle principale dans la fonction main () et la fonction XXX_Control () est appelée dans la boucle principale de la fonction principale.

 void main() { //   XXX XXX_Init(); //   YYY YYY_Init(); //   ZZZ ZZZ_Init(); while(true){ XXX_Control(); YYY_Control(); ZZZ_Control(); } } 

Un fichier de module XXX pourrait ressembler à ceci:

xxx.c

 #include "xxx.h" #define XXX_WATER_MAX_THRESHOLD 100500 #define XXX_WATER_MIN_THRESHOLD 9000 typedef enum{ idle, state1, state2, : stateX, }XXX_States; static XXX_States xxxCurrentState = idle; static int xxxToiletWaterLevel=0; //---------    -------- void private_init1() { } void private_init2() { } void private_measureLevel() { } void private_flush() { } void private_superFlush() { } //--------    --------- void XXX_Init() { xxxCurrentState=idle; } void XXX_Reset() { private_superFlush(); xxxCurrentState=idle; } void XXX_Control() { switch(xxxCurrentState) { case idle: private_measureLevel(); if(xxxToiletWaterLevel>XXX_WATER_MIN_THRESHOLD) xxxCurrentState=state1; break; case state1: if(xxxToiletWaterLevel<XXX_WATER_MAX_THRESHOLD) { private_flush(); xxxCurrentState=idle; } else xxxCurrentState=state2; break; case state2: //   break; } } 

Exemple d'application coopérative multithread


Afin de ne pas être trop abstrait, imaginons un vrai défi. Supposons que nous ayons un flux qui clignote une LED (deux fois par seconde), qui est connecté par une cathode au port PC13. Nous avons également un flux qui reçoit des commandes via le port série. Si le caractère «0» (0x30) arrive, le clignotement s'arrête et un signe «-» est envoyé au client. Si le caractère «1» (0x31) arrive, le clignotement s'allume et le caractère «*» est renvoyé au client. Une pression sur une autre touche renvoie le caractère «E».

Nous placerons le flux de contrôle de la LED clignotante dans les fichiers modLed.h et modLed.c. Ce thread est initialement inactif et ne fait rien. Cependant, sa fonction publique MODLED_command, lorsque l'argument modled_on est reçu, bascule l'état du flux sur
modled_st_on. Dans cet état, le flux allume la LED, se souvient de la valeur initiale du compteur global_count et entre dans l'état de veille modled_st_wait1. Dans cet état, il vérifie en permanence la valeur actuelle du compteur global_count, et lorsque la différence entre le compte courant et le compte initial est MODLED_BLINK_DELAY_ON, le flux passe à l'état modled_st_off. Dans cet état, le flux éteint la LED, se souvient de la valeur actuelle du compte et passe à l'état modled_st_wait2. Dans cet état, le thread compare également la valeur actuelle du compteur global_count avec le compteur initial, et lorsque la différence est MODLED_BLINK_DELAY_OFF, il passe à l'état modled_st_on. Et ainsi, cela continuera jusqu'à ce que quelqu'un appelle la fonction MODLED_command avec l'argument modled_off. Ensuite, la fonction basculera l'état du flux vers modled_st_clamp. Le thread éteindra la LED et passera à l'état modled_st_idle.

L'initialisation du flux modulé commence dans la fonction principale en appelant la fonction MODLED_init (). Cette fonction initialise le port GPIOC et définit l'état initial du flux. Ensuite, dans la boucle, la fonction MODLED_control () est constamment appelée, ce qui en une itération effectue une vérification de l'état actuel et effectue de petites actions pour elle.

Le flux de contrôle du port série est identique.

Il dispose de fonctions d'initialisation de port GPIO privé et d'un module USART1. De plus, à l'intérieur, il est caché un gestionnaire d'interruption du module périphérique USART1, dans lequel l'octet reçu en cours est stocké et l'état du flux est défini sur moduart_st_command.

Initialement, le flux moduart est dans l'état moduart_st_idle, dans lequel il attend la réception d'un octet. Dès qu'un octet est reçu et stocké dans une variable, le gestionnaire d'interruption change l'état du flux en moduart_st_command et le flux vérifie l'octet reçu. Si l'octet reçu est la commande «0», la fonction MODLED_command est appelée avec l'argument modled_off et le caractère «-» est renvoyé. Si l'octet reçu est la commande «1», la fonction MODLED_command est appelée avec l'argument modled_on et le caractère «*» est renvoyé. Dans d'autres cas, le caractère «E» est simplement renvoyé.

L'initialisation du flux MODUART se produit également dans le fichier principal, en appelant la fonction MODUART_init (). Cette fonction initialise le port et le module périphérique USART1 et met le flux en mode veille. Dans la boucle principale, la fonction de contrôle du flux MODUART_control () est appelée, qui vérifie l'état actuel et exécute un petit morceau de code associé à son traitement.

Tout le secret des applications coopératives multi-thread est précisément de créer de petits morceaux de code pour chaque état.

Variable Global_count

Cela vaut probablement une discussion séparée sur cette variable global_count.

Le fichier d'initialisation startup \ startup_stm32f10x_md.s contient une table d'interruption du microcontrôleur. Il contient les adresses des gestionnaires pour toutes les interruptions périphériques
et les noyaux. Cependant, les interruptions de la périphérie ne se produisent que lorsque la périphérie est initialisée. Par conséquent, initialement, les gestionnaires pointent vers des talons temporaires. Mais les gestionnaires d'interruption du noyau Cortex M3 existent vraiment et sont contenus dans le fichier system \ stm32f10x_it . Une telle interruption est l'interruption de la minuterie du système SysTick. Ce temporisateur est utilisé par le RTOS pour appeler le planificateur de tâches. Mais, je l'utilise pour appeler la fonction TimingDelay_Decrement, qui est vraiment définie dans le fichier principal.

 //------------------------------------------------------------------- //   volatile unsigned long global_count=0; //       SysTick,  //    stm32f10x_it.c void TimingDelay_Decrement(void) { //    global_count++; if (TimingDelay != 0x00) { TimingDelay--; } } 

Au début de la fonction principale, la fréquence du minuteur SysTick est réglée sur 1 ms. Par conséquent
tous les millièmes de seconde dans le gestionnaire d'interruption SysTick, le compteur augmente.

Il suffit de se souvenir des valeurs de ce compteur et leur différence donnera l'intervalle de temps entre les vérifications en millisecondes. Ainsi, un compteur peut être utilisé pour résister à des intervalles de temps dans n'importe quel nombre de threads sans appeler un délai de blocage.

Épilogue


Il est possible qu'il existe des solutions plus simples pour connecter PeripheralLib et les fichiers d'initialisation d'horloge initiale. Par exemple, les options des paramètres de projet d'autres environnements de développement ou constantes qui forcent l'environnement de développement à les charger automatiquement lors de la compilation. Cependant, cette méthode, que j'ai citée ici à titre d'exemple, est assez visuelle en soi et permet, si nécessaire, de changer assez rapidement les paramètres d'initialisation du microcontrôleur. Par exemple, refaites l'horloge du générateur interne.

Comparé à d'autres «feux clignotants de LED sur stm32» similaires, qui se trouvent sur Internet, le mien s'est avéré assez lourd. Cependant, sur un tel modèle, je démarre des projets nouveaux et complexes et passer 2 à 5 minutes à le créer ne me semble pas une perte aussi terrible.

Le code source du fichier modLed.h
Fichier ModLed.h
 #ifndef __MODLED_H #define __MODLED_H #include "stm32f10x.h" typedef enum{ modled_off, modled_on, }MODLED_Commands; void MODLED_init(); void MODLED_command(MODLED_Commands aCmd); void MODLED_control(); #endif 


Le code source du fichier modLed.c
Fichier ModLed.c
 #include "modLed.h" #include "main.h" //      #define MODLED_BLINK_DELAY_ON 250 #define MODLED_BLINK_DELAY_OFF 250 //   typedef enum{ modled_st_idle, modled_st_on, modled_st_wait1, modled_st_off, modled_st_wait2, modled_st_clamp, }MODLED_States; //  ,    1  . extern unsigned long global_count; static MODLED_States modledState=modled_st_idle; static uint32_t modledStart, modledEnd; /* PC13 - led (Open drain) */ void modled_init_gpio() { //    GPIOC RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_DeInit(GPIOC); GPIO_InitTypeDef gpio; GPIO_StructInit(&gpio); /*  Blue Pill    ,   PC13. :  PC13      (3 )           .    . */ //   ,  . gpio.GPIO_Mode=GPIO_Mode_Out_OD; gpio.GPIO_Speed=GPIO_Speed_2MHz; gpio.GPIO_Pin=GPIO_Pin_13; GPIO_Init(GPIOC, &gpio); //   GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET); } void MODLED_init() { modled_init_gpio(); modledState=modled_st_idle; modledStart=global_count; } //   . void MODLED_command(MODLED_Commands aCmd) { switch(aCmd) { case modled_on: modledState=modled_st_on; break; case modled_off: modledState=modled_st_clamp; break; } } void MODLED_control() { switch(modledState) { case modled_st_idle: break; case modled_st_on: //     GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET); //     modledStart=global_count; //       modledState=modled_st_wait1; break; case modled_st_wait1: //    modledEnd=global_count; //           if((modledEnd-modledStart)>=MODLED_BLINK_DELAY_ON) { //         modledState=modled_st_off; } break; case modled_st_off: //   GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET); //     modledStart=global_count; //       modledState=modled_st_wait2; break; case modled_st_wait2: //    modledEnd=global_count; //           if((modledEnd-modledStart)>=MODLED_BLINK_DELAY_OFF) { //         modledState=modled_st_on; } break; case modled_st_clamp: //       GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET); modledState=modled_st_idle; break; default: modledState=modled_st_idle; } } 


Le code source du fichier modUart.h
Fichier ModUart.h

 #ifndef __MOD_UART_H #define __MOD_UART_H #include "stm32f10x.h" void MODUART_init(); void MODUART_control(); #endif 


Le code source du fichier modUart.c
Fichier ModUart.c
 #include "modUart.h" #include "modLed.h" #define MODUART_BAUDRATE 115200 typedef enum{ moduart_st_idle, moduart_st_command, }MODUART_STATES; static MODUART_STATES moduartState=moduart_st_idle; static uint16_t moduartCmd=0; /* PA9 UART1_TX PA10 UART1_RX */ void moduart_init_gpio() { //RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //    GPIOA RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef gpio; GPIO_StructInit(&gpio); gpio.GPIO_Mode=GPIO_Mode_AF_PP; gpio.GPIO_Speed=GPIO_Speed_2MHz; gpio.GPIO_Pin=GPIO_Pin_9; GPIO_Init(GPIOA, &gpio); gpio.GPIO_Mode=GPIO_Mode_IN_FLOATING; gpio.GPIO_Pin=GPIO_Pin_10; GPIO_Init(GPIOA, &gpio); } void moduart_init_uart1() { //     UART1 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); USART_InitTypeDef uart; USART_StructInit(&uart); uart.USART_BaudRate=MODUART_BAUDRATE; uart.USART_HardwareFlowControl=USART_HardwareFlowControl_None; uart.USART_Mode=USART_Mode_Rx|USART_Mode_Tx; uart.USART_Parity=USART_Parity_No; uart.USART_StopBits=USART_StopBits_1; uart.USART_WordLength=USART_WordLength_8b; USART_Init(USART1, &uart); //     // --     USART1 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //         NVIC_EnableIRQ(USART1_IRQn); //   USART1. USART_Cmd(USART1, ENABLE); } //    USART1 void USART1_IRQHandler() { //    if(USART_GetITStatus(USART1, USART_IT_RXNE)!=RESET) { //    USART_ClearITPendingBit(USART1, USART_IT_RXNE); //    moduartCmd = USART_ReceiveData(USART1); moduartState = moduart_st_command; } } void moduart_processCmd() { //   uint16_t r = 'E'; switch(moduartCmd) { //    case '0': { MODLED_command(modled_off); r = '-'; } break; //    case '1': { MODLED_command(modled_on); r = '*'; } break; } //    USART_SendData(USART1, r); moduartCmd=0; } void MODUART_init() { moduartState=moduart_st_idle; moduart_init_gpio(); moduart_init_uart1(); } void MODUART_control() { switch(moduartState) { case moduart_st_idle: break; case moduart_st_command: moduart_processCmd(); moduartState=moduart_st_idle; break; default: moduartState=moduart_st_idle; } } 


Le code source du fichier main.c
Fichier main.c
 #include "main.h" #include "modUart.h" #include "modLed.h" // Imported value static __IO uint32_t TimingDelay; RCC_ClocksTypeDef RCC_Clocks; int main() { RCC_GetClocksFreq(&RCC_Clocks); //   SysTick      SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000); //    MODLED_init(); //     MODUART_init(); do{ //      MODLED_control(); //      MODUART_control(); }while(1); #pragma diag_suppress=Pe111 return 0; } //------------------------------------------------------------------- void Delay(__IO uint32_t nCount) { TimingDelay = nCount; while(TimingDelay != 0); } //------------------------------------------------------------------- //   volatile unsigned long global_count=0; //       SysTick,  //    stm32f10x_it.c void TimingDelay_Decrement(void) { //    global_count++; if (TimingDelay != 0x00) { TimingDelay--; } } //------------------------------------------------------------------- #ifdef USE_FULL_ASSERT void assert_failed(uint8_t *file, uint32_t line) { while(1){} } #endif 


Le projet complet peut être téléchargé ici .

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


All Articles