Question

Cette question est la programmation de petits microcontrôleurs sans système d'exploitation. En particulier, je suis intéressé par PICs pour le moment, mais la question est générale.

J'ai vu plusieurs fois le schéma suivant pour garder le temps:

code d'interruption Timer (dire les feux de minuterie à chaque seconde):

...
if (sec_counter > 0)
  sec_counter--;
...

Code réseau principal (non-interruption):

sec_counter = 500; // 500 seconds

while (sec_counter)
{
  // .. do stuff
}

Le code de ligne principale peut répéter, mettre le compteur à différentes valeurs (et pas seulement) secondes et ainsi de suite.

Il me semble qu'il ya une condition de course ici lorsque l'affectation à sec_counter dans le code réseau principal n'est pas atomique. Par exemple, dans PIC18 l'affectation est traduit à 4 déclarations ASM (chargement de chaque octet au moment et en sélectionnant l'octet de droite de la banque de mémoire avant que). Si le code d'interruption est au milieu de cela, la valeur finale peut être corrompue.

Curieusement, si la valeur attribuée est inférieure à 256, l'affectation est atomique, donc il n'y a pas de problème.

Ai-je raison de ce problème? Quels modèles utilisez-vous pour mettre en œuvre correctement un tel comportement? Je vois plusieurs options:

  • Désactiver interrompt avant chaque affectation à sec_counter et permettre après - ce n'est pas assez
  • Ne pas utiliser une interruption, mais une minuterie séparée qui est démarré puis sondé. Ceci est propre, mais utilise une minuterie tout (dans le cas précédent le minuteur de cuisson 1 sec peut être utilisé à d'autres fins aussi bien).

D'autres idées?

Était-ce utile?

La solution

L'architecture PIC est aussi atomique qu'il obtient. Il veille à ce que toutes les opérations de lecture-écriture-modification dans un fichier de mémoire sont « atomique ». Bien qu'il faut 4 horloges pour effectuer toute lecture-écriture-modification, les 4 horloges sont consommées en une seule instruction et l'instruction suivante utilise le prochain cycle d'horloge 4. Il est la façon dont fonctionne le pipeline. Dans 8 horloges, deux instructions sont en cours.

Si la valeur est supérieure à 8 bits, il devient un problème que le PIC est une machine 8 bits et opérandes plus grands sont traités dans plusieurs instructions. Cela introduira des questions atomiques.

Autres conseils

Ecrire la valeur puis vérifiez qu'il est la valeur requise semble être la plus simple alternative.

do {
 sec_counter = value;
} while (sec_counter != value);

BTW, vous devez faire la variable volatile en cas d'utilisation C.

Si vous avez besoin de lire la valeur, vous pouvez le lire deux fois.

do {
    value = sec_counter;
} while (value != sec_counter);

Vous devez certainement désactiver l'interruption avant de le compteur. Laid comme il peut être, il est nécessaire. Il est une bonne pratique de désactiver toujours l'interruption avant de configurer les registres du matériel ou des variables logicielles affectant la méthode ISR. Si vous écrivez en C, vous devriez considérer toutes les opérations comme non-atomique. Si vous trouvez que vous devez regarder l'assembly généré trop de fois, alors il peut être préférable d'abandonner C et le programme dans l'assemblage. Dans mon expérience, ce qui est rarement le cas.

En ce qui concerne la question discutée, voici ce que je suggère:

ISR:
if (countDownFlag)
{
   sec_counter--;
}

et le réglage du compteur:

// make sure the countdown isn't running
sec_counter = 500;
countDownFlag = true;

...
// Countdown finished
countDownFlag = false;

Vous avez besoin d'une variable supplémentaire et est préférable de tout envelopper dans une fonction:

void startCountDown(int startValue)
{
    sec_counter = 500;
    countDownFlag = true;
}

De cette façon, vous la méthode abstraite de départ (et cacher la laideur si nécessaire). Par exemple, vous pouvez facilement changer pour démarrer une horloge matérielle sans affecter les appelants de la méthode.

Parce que les accès à la variable sec_counter ne sont pas atomiques, il n'y a vraiment aucun moyen d'éviter les interruptions de désactiver avant d'accéder à cette variable dans votre code de ligne principale et le rétablissement de l'état d'interruption après l'accès si vous souhaitez un comportement déterministe. Ce serait probablement un meilleur choix que de consacrer à cette tâche une minuterie HW (sauf si vous avez un excédent de minuteries, auquel cas vous pourriez aussi bien utiliser un).

Si vous téléchargez pile TCP / IP libre de Microchip il y a des routines là-dedans qui utilisent un débordement de minuterie pour garder la trace du temps écoulé. Plus précisément "tick.c" et "tick.h". Il suffit de copier les fichiers sur votre projet.

Dans ces fichiers, vous pouvez voir comment ils le font.

Il est pas si curieux de connaître les moins de 256 mouvements étant atomique - le déplacement d'une valeur de 8 bits est un opcode si c'est aussi atomique que vous obtenez.

La meilleure solution sur un microcontrôleur PIC comme est de désactiver les interruptions avant de modifier la valeur de la minuterie. Vous pouvez même vérifier la valeur du drapeau d'interruption lorsque vous modifiez la variable dans la boucle principale et le manipuler si vous voulez. Faites-en une fonction qui modifie la valeur de la variable et vous pouvez même l'appeler de la ISR ainsi.

Eh bien, qu'est-ce que l'apparence de code assembleur de comparaison comme?

prises pour compte qu'il compte vers le bas, et qu'il est juste un zéro comparer, il doit être sûr s'il vérifie d'abord le MSB, le bit de poids faible. Il pourrait y avoir la corruption, mais il n'a pas vraiment si elle est au milieu entre 0x100 et 0xff et la valeur est corrompue comparer 0x1ff.

La façon dont vous utilisez votre minuterie maintenant, il ne comptera pas de toute façon secondes entières, parce que vous pourriez changer au milieu d'un cycle. Donc, si vous ne vous inquiétez pas. La meilleure façon, à mon avis, serait de lire la valeur, puis il suffit de comparer la différence. Il prend deux plus OPs, mais n'a pas de problèmes multi-threading. (Etant donné que la minuterie est prioritaire)

Si vous êtes plus stricte de la valeur de temps, je désactive automatiquement la minuterie une fois le compte à rebours à 0, et le compteur à zéro interne de la minuterie et une fois que vous activer besoin.

Déplacer la partie de code qui serait le principal () à une fonction appropriée, et l'ont appelé conditionnellement par la RSR.

En outre, pour éviter toute sorte de retarder ou manquants tiques, choisissez cette ISR minuterie pour être un haut-prio interruption (PIC18 a deux niveaux).

Une approche est d'avoir une interruption de garder une variable d'octet, et avoir quelque chose d'autre qui est appelé au moins une fois tous les 256 fois le compteur est frappé; faire quelque chose comme:

// ub==unsigned char; ui==unsigned int; ul==unsigned long
ub now_ctr; // This one is hit by the interrupt
ub prev_ctr;
ul big_ctr;

void poll_counter(void)
{
  ub delta_ctr;

  delta_ctr = (ub)(now_ctr-prev_ctr);
  big_ctr += delta_ctr;
  prev_ctr += delta_ctr;
}

Une légère variation, si vous ne me dérange pas de forcer le compteur de l'interruption de rester en phase avec le bit de poids faible de votre grand compteur:

ul big_ctr;
void poll_counter(void)
{
  big_ctr += (ub)(now_ctr - big_ctr);
}

Personne n'abordé la question de lire registres matériels multi-octets (par exemple une minuterie. La minuterie pourrait rouler et incrémenter son deuxième octet pendant que vous le lisez.

Disons que c'est 0x0001ffff et vous le lire. Vous pouvez obtenir 0x0010ffff ou 0x00010000.

Le registre périphérique 16 bits est volatile à votre code.

Pour tout volatile "variables", j'utilise la technique de double lecture.

do {
       t = timer;
 } while (t != timer);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top