Existe-t-il un bon moyen de mettre en œuvre la communication entre un ISR et le reste du programme pour un système embarqué qui évite les variables globales?
Il semble que le modèle général consiste à avoir une variable globale qui est partagée entre l'ISR et le reste du programme et utilisée comme indicateur, mais cette utilisation de variables globales va à l'encontre de moi. J'ai inclus un exemple simple utilisant des ISR de style avr-libc:
volatile uint8_t flag;
int main() {
...
if (flag == 1) {
...
}
...
}
ISR(...) {
...
flag = 1;
...
}
Je ne peux pas voir de loin ce qui est essentiellement un problème de portée; toutes les variables accessibles à la fois par l'ISR et le reste du programme doivent être intrinsèquement globales, sûrement? Malgré cela, j'ai souvent vu des gens dire des choses comme «les variables globales sont un moyen de mettre en œuvre la communication entre les ISR et le reste du programme» (c'est moi qui souligne), ce qui semble impliquer qu'il existe d'autres méthodes; s'il existe d'autres méthodes, quelles sont-elles?
Réponses:
Il existe une méthode standard de facto pour ce faire (en supposant une programmation C):
extern
mot-clé ou simplement par erreur.static
. Une telle variable n'est pas globale mais limitée au fichier où elle est déclarée.volatile
. Remarque: cela ne donne pas d'accès atomique ni ne résout la rentrée!la source
inline
devienne obsolète, car les compilateurs optimisent de plus en plus le code. Je dirais que s'inquiéter de la surcharge est une "optimisation prématurée" - dans la plupart des cas, la surcharge n'a pas d'importance, si elle est même présente dans le code machine.Tel est le vrai problème. Passer à autre chose.
Maintenant, avant que les genoux ne se plaignent immédiatement de la façon dont cela est impur, permettez-moi de nuancer un peu. Il y a certainement un danger à utiliser des variables globales à outrance. Mais ils peuvent également augmenter l'efficacité, ce qui compte parfois dans les petits systèmes aux ressources limitées.
La clé est de penser à quand vous pouvez raisonnablement les utiliser et ne risquez pas de vous causer des ennuis, par rapport à un bug qui ne fait qu'attendre. Il y a toujours des compromis. Bien qu'éviter généralement les variables globales pour communiquer entre l'interruption et le code de premier plan est une ligne directrice insoutenable, la prendre, comme la plupart des autres lignes directrices, à un extrême religieux est contre-productif.
Voici quelques exemples où j'utilise parfois des variables globales pour transmettre des informations entre le code d'interruption et de premier plan:
Je m'assure que les graduations de 1 ms, 10 ms et 100 ms sont d'une taille de mot qui peut être lue en une seule opération atomique. Si vous utilisez un langage de haut niveau, assurez-vous d'indiquer au compilateur que ces variables peuvent changer de manière asynchrone. En C, par exemple , vous les déclarez volatiles externes . Bien sûr, c'est quelque chose qui va dans un fichier include en conserve, vous n'avez donc pas besoin de vous en souvenir pour chaque projet.
Je fais parfois le compteur de ticks 1 s le compteur de temps total écoulé, alors faites 32 bits de large. Cela ne peut pas être lu en une seule opération atomique sur la plupart des petits micro que j'utilise, donc ce n'est pas rendu mondial. Au lieu de cela, une routine est fournie qui lit la valeur de plusieurs mots, traite des mises à jour possibles entre les lectures et renvoie le résultat.
Bien sûr, il aurait pu y avoir des routines pour obtenir les plus petits compteurs de 1 ms, 10 ms, etc. Cependant, cela ne fait vraiment pas grand-chose pour vous, ajoute de nombreuses instructions au lieu de lire un seul mot et utilise un autre emplacement de pile d'appels.
Quel est l'inconvénient? Je suppose que quelqu'un pourrait faire une faute de frappe qui écrit accidentellement sur l'un des compteurs, ce qui pourrait alors gâcher un autre timing dans le système. Écrire délibérément sur un compteur n'aurait aucun sens, donc ce type de bogue devrait être quelque chose de non intentionnel comme une faute de frappe. Semble très improbable. Je ne me souviens pas que cela se soit produit dans plus de 100 petits projets de microcontrôleurs.
Par exemple, l'A / D peut lire la sortie 0 à 3 V d'un diviseur de tension pour mesurer l'alimentation 24 V. Les nombreuses lectures sont exécutées à travers un certain filtrage, puis mises à l'échelle de sorte que la valeur finale soit en millivolts. Si l'alimentation est à 24,015 V, la valeur finale est 24015.
Le reste du système ne voit qu'une valeur actualisée en direct indiquant la tension d'alimentation. Il ne sait pas et n'a pas besoin de se soucier quand exactement cela est mis à jour, d'autant plus qu'il est mis à jour beaucoup plus souvent que le temps d'établissement du filtre passe-bas.
Encore une fois, une routine d'interface peut être utilisée, mais vous en tirez très peu d'avantages. L'utilisation de la variable globale chaque fois que vous avez besoin de la tension d'alimentation est beaucoup plus simple. N'oubliez pas que la simplicité n'est pas seulement pour la machine, mais que plus simple signifie également moins de risques d'erreur humaine.
la source
extern int ticks10ms
parinline int getTicks10ms()
ne fera absolument aucune différence dans l'assemblage compilé, tandis que d'un autre côté, il sera difficile de modifier accidentellement sa valeur dans d'autres parties du programme, et vous permettra également de un moyen de "se connecter" à cet appel (par exemple pour se moquer du temps pendant les tests unitaires, pour consigner l'accès à cette variable, ou autre). Même si vous soutenez que la possibilité pour un programmeur san de changer cette variable à zéro, il n'y a pas de coût pour un getter en ligne.Toute interruption particulière sera une ressource globale. Parfois, cependant, il peut être utile que plusieurs interruptions partagent le même code. Par exemple, un système peut avoir plusieurs UART, qui doivent tous utiliser une logique d'envoi / réception similaire.
Une bonne approche pour gérer cela consiste à placer les éléments utilisés par le gestionnaire d'interruption, ou des pointeurs vers eux, dans un objet de structure, puis à ce que les gestionnaires d'interruption matériels réels soient quelque chose comme:
Les objets
uart1_info
,uart2_info
etc. seraient des variables globales, mais ce seraient les seules variables globales utilisées par les gestionnaires d'interruption. Tout le reste que les gestionnaires vont toucher serait traité dans ces limites.Notez que tout ce qui est accessible à la fois par le gestionnaire d'interruption et par le code de la ligne principale doit être qualifié
volatile
. Il peut être plus simple de déclarer simplementvolatile
tout ce qui sera utilisé par le gestionnaire d'interruption, mais si les performances sont importantes, il peut être utile d'écrire du code qui copie les informations dans des valeurs temporaires, les opère, puis les réécrit. Par exemple, au lieu d'écrire:écrire:
La première approche peut être plus facile à lire et à comprendre, mais sera moins efficace que la seconde. La question de savoir si cela dépend de l'application.
la source
Voici trois idées:
Déclarez la variable indicateur comme statique pour limiter la portée à un seul fichier.
Rendez la variable d'indicateur privée et utilisez les fonctions getter et setter pour accéder à la valeur d'indicateur.
Utilisez un objet de signalisation tel qu'un sémaphore au lieu d'une variable indicateur. L'ISR définirait / publierait le sémaphore.
la source
Une interruption (c'est-à-dire le vecteur qui pointe vers votre gestionnaire) est une ressource globale. Donc, même si vous utilisez une variable sur la pile ou sur le tas:
ou du code orienté objet avec une fonction 'virtuelle':
… La première étape doit impliquer une variable globale réelle (ou au moins statique) pour atteindre ces autres données.
Tous ces mécanismes ajoutent une indirection, ce qui n'est généralement pas le cas si vous souhaitez extraire le dernier cycle du gestionnaire d'interruption.
la source
Je suis en train de coder pour Cortex M0 / M4 en ce moment et l'approche que nous utilisons en C ++ (il n'y a pas de balise C ++, donc cette réponse pourrait être hors sujet) est la suivante:
Nous utilisons une classe
CInterruptVectorTable
qui contient toutes les routines de service d'interruption qui sont stockées dans le vecteur d'interruption réel du contrôleur:La classe
CInterruptVectorTable
implémente une abstraction des vecteurs d'interruption, vous pouvez donc lier différentes fonctions aux vecteurs d'interruption pendant l'exécution.L'interface de cette classe ressemble à ceci:
Vous devez créer les fonctions qui sont stockées dans la table vectorielle
static
car le contrôleur ne peut pas fournir dethis
pointeur car la table vectorielle n'est pas un objet. Donc, pour contourner ce problème, nous avons lepThis
pointeur statique à l'intérieur duCInterruptVectorTable
. En entrant dans l'une des fonctions d'interruption statiques, il peut accéder aupThis
pointeur pour accéder aux membres du même objetCInterruptVectorTable
.Maintenant, dans le programme, vous pouvez utiliser le
SetIsrCallbackfunction
pour fournir un pointeur de fonction à unestatic
fonction qui doit être appelée lorsqu'une interruption se produit. Les pointeurs sont stockés dans leInterruptVectorTable_t virtualVectorTable
.Et l'implémentation d'une fonction d'interruption ressemble à ceci:
Donc, cela appellera une
static
méthode d'une autre classe (qui peut êtreprivate
), qui peut alors contenir un autrestatic
this
pointeur pour accéder aux variables membres de cet objet (un seul).Je suppose que vous pourriez construire et interfacer comme
IInterruptHandler
et stocker des pointeurs vers des objets, donc vous n'avez pas besoin dustatic
this
pointeur dans toutes ces classes. (peut-être essayons-nous cela dans la prochaine itération de notre architecture)L'autre approche fonctionne bien pour nous, car les seuls objets autorisés à implémenter un gestionnaire d'interruption sont ceux à l'intérieur de la couche d'abstraction matérielle, et nous n'avons généralement qu'un seul objet pour chaque bloc matériel, donc cela fonctionne bien avec les
static
this
pointeurs. Et la couche d'abstraction matérielle fournit une autre abstraction aux interruptions, appeléeICallback
qui est ensuite implémentée dans la couche de périphérique au-dessus du matériel.Accédez-vous aux données globales? Bien sûr, mais vous pouvez rendre privées la plupart des données globales nécessaires, comme les
this
pointeurs et les fonctions d'interruption.Ce n'est pas à l'épreuve des balles, et cela ajoute des frais généraux. Vous aurez du mal à implémenter une pile IO-Link en utilisant cette approche. Mais si vous n'êtes pas extrêmement serré avec les timings, cela fonctionne très bien pour obtenir une abstraction flexible des interruptions et de la communication dans les modules sans utiliser de variables globales accessibles de partout.
la source