2017-02-17 4 views
5

J'écris une interruption de microcontrôleur qui doit ajouter un décalage à l'une de ses minuteries de matériel. Cependant, en raison de la façon dont fonctionne le temporisateur de temporisation, l'approche naïve peut introduire une erreur hors fonction en fonction du moment de l'exécution de l'interruption par rapport à l'horloge du prédicateur.Comment puis-je éviter cette erreur lors de l'ajout d'un décalage à une minuterie matérielle pré-échelonnée?

timing diagram of ISR off-by-one error

J'utilise la minuterie 1 sur un ATmega328P (= arduino) pour cela. Je l'ai mis en place en mode normal avec un prescaler/8, et j'utilise l'interruption de capture de minuterie pour déclencher cela; le but de l'interruption est de régler le temporisateur pour déborder exactement period cycles après l'événement qui déclenche la capture d'entrée (dans le cas où le déclenchement se produit pendant une autre interruption ou une autre situation dans laquelle les interruptions sont désactivées).

(J'utilise abusivement la sortie PWM pour déclencher deux optotriacs secteur à un décalage de phase CA variable, sans avoir besoin de brûler tout le temps CPU, l'interruption est déclenchée par un détecteur de passage par zéro sur la phase réseau.

le code de la ISR serait quelque chose comme ceci:

uint_16 period = 16667; 

ISR(TIMER1_CAPT_vect){ 
    TCNT1 = TCNT1 - ICR1 - period + (elapsed counter ticks during execution); 
} 

l'intervalle critique est ici celui entre le moment où TCNT1 est lu à partir et quand il est ensuite écrit à nouveau. Autant que je sache, il n'y a aucun moyen de lire directement l'état du prédiviseur, donc je ne pense pas qu'il soit possible d'appliquer un décalage différent en fonction de la synchronisation ISR.

Je pourrais simplement réinitialiser le prédiviseur avant le ISR (GTCCR |= _BV(TSM); GTCCR |= _BV(PSRSYNC); GTCCR &= ~_BV(TSM);) pour synchroniser, mais cela introduit toujours un décalage aléatoire à la minuterie qui dépend de la synchronisation ISR.

Une autre approche que je considère est d'utiliser une minuterie pour générer une interruption synchronisée avec le prédiviseur. J'utilise déjà les deux registres de comparaison de sortie sur le timer 1, mais le timer 0 partage le prescaler pour qu'il puisse être utilisé. Cependant, l'exécution de l'interruption de la minuterie pourrait être différée par une autre interruption ou un bloc 'cli', ce qui ne garantit pas le bon fonctionnement.

Comment puis-je écrire mon interruption pour éviter ce bug?

+1

Je suppose que le calcul 'TCNT1' n'est pas toujours désactivé par un (timing 2?), Juste _sometimes_ (timing 1)? – chux

+0

@chux Oui, TCNT1 n'est parfois désactivé que par un. Je ne sais pas vraiment comment l'expliquer avec des mots, mais le chronogramme que j'ai inclus montre comment cela se passe. – AJMansfield

+0

La routine ISR est-elle assez rapide pour échantillonner et détecter une impulsion 'clk_Tn'? Est-ce que 'clk_Tn' peut définir un drapeau qui peut être effacé par l'ISR? – chux

Répondre

1

Si vous écrivez l'ISR comme

ISR(TIMER1_CAPT_vect){ 
    int counter = TCNT1 - ICR1 - period + 3; 
    asm("nop"); 
    asm("nop"); 
    TCNT1 = counter; 
} 

l'écriture de TCNT1 doit avoir lieu exactement 24 cycles après le registre en cours de lecture, donc à la prédiviseur « phase ». (Le nombre de nop peut être ajusté si nécessaire, par exemple en raison de variations entre différents types de microcontrôleurs). Cependant, la solution n'est pas en mesure de prendre en compte la modification de la «phase» du prédiviseur qui se produit entre le réglage de ICR1 et la lecture de TCNT1.