Sections critiques sur Cortex-M3

10

Je m'interroge un peu sur l'implémentation de sections de code critiques sur un Cortex-M3 où les exceptions ne sont pas autorisées en raison de contraintes de temps ou de problèmes de concurrence.

Dans mon cas, j'utilise un LPC1758 et j'ai un émetteur-récepteur TI CC2500 à bord. Le CC2500 possède des broches qui peuvent être utilisées comme lignes d'interruption pour les données dans le tampon RX et l'espace libre dans le tampon TX.

Par exemple, je veux avoir un tampon TX dans SRAM de mon MCU et quand il y a de l'espace libre dans le tampon TX de l'émetteur-récepteur, je veux y écrire ces données. Mais la routine qui place les données dans le tampon SRAM ne peut évidemment pas être interrompue par l'interruption de l'espace libre en TX. Donc, ce que je veux faire, c'est de désactiver temporairement les interruptions lors de cette procédure de remplissage de ce tampon, mais d'exécuter toutes les interruptions qui se produisent pendant cette procédure après sa fin.

Comment cela se fait-il le mieux sur Cortex-M3?

Emil Eriksson
la source

Réponses:

11

Le Cortex M3 prend en charge une paire utile d'opérations (communes à de nombreuses autres machines également) appelées "Load-Exclusive" (LDREX) et "Store-Exclusive" (STREX). Sur le plan conceptuel, l'opération LDREX effectue une charge, définit également un matériel spécial pour observer si l'emplacement qui a été chargé peut être écrit par autre chose. L'exécution d'un STREX à l'adresse utilisée par le dernier LDREX entraînera l'écriture de cette adresse uniquement si rien d'autre ne l'a écrite en premier . L'instruction STREX chargera un registre avec 0 si le stockage a eu lieu, ou 1 s'il a été abandonné.

Notez que STREX est souvent pessimiste. Il existe une variété de situations où il peut décider de ne pas exécuter le magasin même si l'emplacement en question n'a pas été effectivement touché. Par exemple, une interruption entre un LDREX et un STREX amènera le STREX à supposer que l'emplacement surveillé a peut-être été atteint. Pour cette raison, c'est généralement une bonne idée de minimiser la quantité de code entre LDREX et STREX. Par exemple, considérez quelque chose comme ceci:

inline void safe_increment (uint32_t * addr)
{
  uint32_t new_value;
  faire
  {
    new_value = __ldrex (addr) + 1;
  } while (__ strex (nouvelle_valeur, addr));
}

qui se compile en quelque chose comme:

; Supposons que R0 détient l'adresse en question; r1 saccagé
lp:
  ldrex r1, [r0]
  ajouter r1, r1, # 1
  strex r1, r1, [r0]
  cmp r1, # 0; Test si différent de zéro
  bne lp
  .. le code continue

La grande majorité du temps où le code s'exécute, rien ne se passera entre le LDREX et le STREX pour les "déranger", donc le STREX réussira sans plus attendre. Si, cependant, une interruption se produit immédiatement après l'instruction LDREX ou ADD, le STREX n'effectuera pas le stockage, mais à la place, le code reviendra pour lire la valeur (éventuellement mise à jour) de [r0] et calculer une nouvelle valeur incrémentée sur la base de cela.

L'utilisation de LDREX / STREX pour former des opérations comme safe_increment permet non seulement de gérer les sections critiquesm, mais aussi dans de nombreux cas d'éviter leur besoin.

supercat
la source
Il n'y a donc aucun moyen de "bloquer" les interruptions pour qu'elles puissent être à nouveau servies une fois débloquées? Je me rends compte que c'est probablement une solution inélégante même si possible mais je veux juste en savoir plus sur la gestion des interruptions ARM.
Emil Eriksson
3
Il est possible de désactiver les interruptions, et sur le Cortex-M0, il n'y a souvent aucune alternative pratique à le faire. Je considère que l'approche LDREX / STREX est plus propre que la désactivation des interruptions, mais il est vrai que dans de nombreux cas, cela n'aura pas vraiment d'importance (je pense que l'activation et la désactivation finissent par être un cycle chacune, et la désactivation des interruptions pour cinq cycles n'est probablement pas un gros problème) . Notez qu'une approche ldrex / strex fonctionnera si le code est migré vers un processeur multicœur, contrairement à une approche qui désactive les interruptions. En outre, certains codes d'exécution de RTOS avec des autorisations réduites ne sont pas autorisés à désactiver les interruptions.
supercat
Je finirai probablement par utiliser FreeRTOS de toute façon, donc je ne le ferai pas moi-même mais j'aimerais quand même apprendre. Quelle méthode de désactivation des interruptions dois-je utiliser pour bloquer les interruptions comme décrit, par opposition à annuler toutes les interruptions survenues pendant la procédure? Comment pourrais-je le faire si je veux les jeter?
Emil Eriksson
La seule réponse ci-dessus n'est pas digne de confiance car il manque une parenthèse au code associé: while(STREXW(new_value, addr); comment pouvons-nous croire que ce que vous dites est correct si votre code ne compile même pas?
@Tim: Désolé, ma saisie n'est pas parfaite; Je n'ai pas le code que j'ai écrit à portée de main pour la comparaison, donc je ne me souviens pas si le système que j'utilisais utilisait STREXW ou __STREXW, mais la référence du compilateur répertorie __strex comme intrinsèque (contrairement à STREXW qui est limité à STREX 32 bits, les utilisations intrinsèques de __strex génèrent un STREXB, STREXH ou STREX selon la taille du pointeur fourni)
supercat
4

Il semble que vous ayez besoin de tampons circulaires ou FIFO dans votre logiciel MCU. En suivant deux index ou pointeurs dans le tableau pour la lecture et l'écriture, vous pouvez avoir à la fois l'avant-plan et l'arrière-plan accédant au même tampon sans interférence.

Le code de premier plan est libre d'écrire dans le tampon circulaire à tout moment. Il insère des données au pointeur d'écriture, puis incrémente le pointeur d'écriture.

Le code d'arrière-plan (gestion des interruptions) consomme des données du pointeur de lecture et incrémente le pointeur de lecture.

Lorsque les pointeurs de lecture et d'écriture sont égaux, le tampon est vide et le processus d'arrière-plan n'envoie aucune donnée. Lorsque le tampon est plein, le processus de premier plan refuse d'écrire davantage (ou peut écraser les anciennes données, selon vos besoins).

L'utilisation de tampons circulaires pour découpler les lecteurs et les écrivains devrait supprimer la nécessité de désactiver les interruptions.

Toby Jaffey
la source
Oui, je vais évidemment utiliser des tampons circulaires mais l'incrémentation et la décrémentation ne sont pas des opérations atomiques.
Emil Eriksson
3
@Emil: Ils ne doivent pas l'être. Pour un tampon circulaire classique, avec deux pointeurs et un emplacement "inutilisable", il suffit que les écritures en mémoire soient atomiques et appliquées dans l'ordre. Le lecteur possède un pointeur, l'écrivain possède l'autre et, bien que les deux puissent lire l'un ou l'autre pointeur, seul le propriétaire du pointeur écrit son pointeur. À ce stade, vous n'avez besoin que d'écritures atomiques dans l'ordre.
John R. Strohm
2

Je ne me souviens pas de l'emplacement exact mais dans les bibliothèques qui proviennent d'ARM (pas TI, ARM, il devrait être sous CMSIS ou quelque chose comme ça, j'utilise ST mais je me souviens avoir lu quelque part que ce fichier venait d'ARM donc vous devriez l'avoir aussi ) il existe une option globale de désactivation des interruptions. Il s'agit d'un appel de fonction. (Je ne suis pas au travail mais je chercherai demain la fonction exacte). Je terminerais avec un joli nom dans votre système et désactiverais les interruptions, refaites votre travail et réactivez. Cela dit, la meilleure option serait d'implémenter un sémaphore ou une structure de file d'attente au lieu de désactiver l'interruption globale.

Ktc
la source