0
votes

ATTINY85, la minuterie microseconde mise en œuvre sur Timer0 ne compte pas la bonne heure

Il y a ATTINY85, avec une source d'horloge interne à 8 MHz.

J'essaie de mettre en œuvre une minuterie microseconde basée sur la minuterie de la minuterie matérielle0. P>

Quelle est ma logique: Étant donné que la fréquence d'horloge est de 8 MHz et que le prescalaire est éteint, l'heure d'un cycle d'horloge sera d'environ 0,1 ° (1/8000000). Initialement, la minuterie déborde et provoque des interruptions lors du passage de 0 ... 255, il faut plus de 0,1 ° et n'est pas pratique pour calculer 1î¼s. P>

Pour résoudre ceci, j'ai pensé à la possibilité de modifier la valeur initiale. de la minuterie au lieu de 0 à 245. Il s'avère que pour aller à l'interruption, vous devez passer à 10 cycles d'horloge, ce qui prend environ 1us à temps. P>

Je charge ce code, mais Le voyant Attiny ne bascule évidemment pas environ 5 secondes, bien que le code indique 1 seconde (1000000US). P>

code: strong>

#include <avr/io.h>
#undef F_CPU
#define F_CPU 8000000UL
#include <avr/interrupt.h>


// Timer0 init
void timer0_Init() {
    cli();
    //SREG &= ~(1 << 7);
    // Enable interrupt for timer0 overflow
    TIMSK |= (1 << 1);
    // Enabled timer0 (not prescaler) - CS02..CS00 = 001
    TCCR0B = 0;
    TCCR0B |= (1 << 0);
    // Clear timer0 counter
    TCNT0 = 245;
    sei();
    //SREG |= (1 << 7);
}

// timer0 overflow interrupt
// 1us interval logic:
// MCU frequency = 8mHz (8000000Hz), not prescaler
// 1 tick = 1/8000000 = 100ns = 0.1us, counter up++ after 1 tick (0.1us)
// 1us timer = 10 tick's => 245..255
static unsigned long microsecondsTimer;
ISR(TIMER0_OVF_vect) {
    microsecondsTimer++;
    TCNT0 = 245;
}

// Millis
/*unsigned long timerMillis() {
   return microsecondsTimer / 1000;
}*/

void ledBlink() {
    static unsigned long blinkTimer;
    static int ledState;
    // 10000us = 0.01s
    // 1000000us = 1s
    if(microsecondsTimer - blinkTimer >= 1000000) {
        if(!ledState) {
            PORTB |= (1 << 3); // HIGH
            } else {
            PORTB &= ~(1 << 3); // LOW
        }
        ledState = !ledState;
        blinkTimer = microsecondsTimer;
    }
}


int main(void)
{
    
    // Set LED pin to OUTPUT mode
    DDRB |= (1 << 3);
    
    timer0_Init();

    while (1)
    {
        ledBlink();
    }
}


4 commentaires

Votre timing est défini par le temps d'exécution de l'ISR de plus de 10 cycles. En effet, il suffit d'entrer l'interruption (sauvegarde d'un PC, d'une extraction de vecteur), d'exécution de vecteur (RJMP à ISR) et reti à l'extrémité ISR consomme 10 cycles.


Veuillez ajouter c tag dans la question pour colorer les échantillons de code ..


Commencez par étudier combien de temps votre latence d'interruption attendue est. En termes de matériel et de logiciels à la fois. Sur une architecture lente et obsolète de 8 bits comme AVR, il va être beaucoup plus que 1us.


Lire la fiche technique. Vous ne pouvez pas obtenir une précision de 1 microseconde avec une horloge AVR et une horloge de 8 MHz. Lire pour savoir pourquoi.


3 Réponses :


2
votes

Comme indiqué par @reah, votre ISR n'a pas assez de temps pour courir. Votre ISR prendra plus de 1 microseconde pour exécuter et revenir, vous avez donc toujours des interruptions manquantes.

Il y a d'autres problèmes ici aussi. Par exemple, votre variable microsecondstimer long . Long Les variables sont de 4 octets larges et ne sont donc pas mises à jour atomiquement. Il est possible, par exemple, que votre premier plan pouvait commencer à lire la valeur de microsecondstimer , puis au milieu de la lecture, l'ISR pourrait mettre à jour certains des octets non lus, puis lorsque le premier plan démarre à nouveau. Cela se retrouvera avec une valeur mutilée. En outre, vous devriez éviter de jouer avec le registre du comte depuis la mise à jour des tiques qui peuvent manquer si vous êtes très prudent.

Alors, comment pouvez-vous mettre en œuvre une minuterie USEC de travail? Tout d'abord, vous souhaitez appeler l'ISR comme étant le plus rarement possible, alors peut-être choisir le plus grand prescalteur que vous pouvez obtenir obtenir la résolution que vous souhaitez et uniquement ISR sur le trop-plein. Dans le cas de la minuterie ATTINY85, vous pouvez choisir / 8 Prescaller qui vous obtient une seule case de la minuterie par microseconde avec une horloge système de 8 MHz. Maintenant, votre ISR fonctionne uniquement une fois tous les 256 microsecondes et lorsqu'il est exécuté, il n'a besoin que d'incrémenter un compteur "microsecondes * 256" dans chaque appel.

Maintenant pour lire les microsecondes actuels au premier plan, vous pouvez obtenir le numéro de Microseconds MOD 256 en lisant directement le registre de comptage, puis lisez le compteur "microsecondes * 256" et multiplie cela par 256 et ajoutez que le compteur et vous aurez le comptage complet. Notez que vous aurez besoin de prendre des précautions spéciales pour vous assurer que vos lectures sont atomiques. Vous pouvez le faire soit en éteignant soigneusement les interruptions, en lisant rapidement les valeurs, puis en retournant les interruptions (sauvegardez tous les mathématiques pour quand les interruptions sont de retour) ou en boucle sur les valeurs de lecture pour vous assurer que vous obtenez deux lit dans une rangée identique (moyen de temps qui ne sont pas mis à jour pendant que vous les lisiez).

Notez que vous pouvez consulter le code source sur Arduino Timer ISR Pour quelques idées, mais notez que le leur est plus compliqué car il peut gérer une large gamme de vitesses de ticket alors que vous êtes capable de garder les choses simples en choisissant spécifiquement une période de 1us.


1 commentaires

Merci pour la réponse! Maintenant, j'ai étudié le code source Timer0 dans Arduino Core, installé le 64 Prescaler et ça marche! Code - Pastebin.com/8RT10ARU



1
votes

Pourquoi vous n'avez pas utilisé de pré-scaler ?!

Votre code Besoin d'un défi de retard grisé (1 seconde est-il énorme en fonction de la vitesse de la CPU) ... Donc, ce n'est pas la sagesse choisit d'interrompre le microcontrôleur tous les 1 US! 8 Mega Hz CODE> Donc, si nous avons choisi le prescaller à 64 code>, l'horloge de la minuterie sera alors 8mHz / 64 code> = 125 kHz code> de sorte que moyenne de chaque heure TIK (horloge de la minuterie) sera 1/125kHz code> = 8 US code> p>

donc si nous aimons avoir InturPT tous les 1ms alors nous avons besoin 125 tik code> p>

modifier le code h1>

Essayez ce code, il est plus clair pour comprendre p>


    #undef F_CPU
    #define F_CPU 8000000UL
    
    #include <avr/io.h>
    #include <avr/interrupt.h>
    
    volatile int millSec;
    
    void timer0_Init();
    void toggleLed();
    
    int main(void)
    {
        
        // Set LED pin to OUTPUT mode
        DDRB |= (1 << 3);
        
        timer0_Init();
        millSec = 0;  // init the millsecond
        
        sei(); // set Global Interrupt Enable 
    
        while (1)
        {
            if(millSec >= 1000){
                // this block of code will run every 1 sec          
                millSec =0; // start count for the new sec
                toggleLed(); // just toggle the led state           
            }
            // Do other backGround jobs
        }
    }
    
    
   //#####Helper functions###########
    
    void timer0_Init() {
        
        // Clear timer0 counter
        TCNT0 = 130;  //255-125=130
        
        // Enable interrupt for timer0 overflow
        TIMSK = (1 << 1);
        
        // set  prescaler to 64 and start the timer
        TCCR0B = (1<<CS00)|(1<<CS01);
        
        
        
    }
    
    void toggleLed(){
        PORTB ^= (1 << 3); // toggle led output
    }
    
    ISR(TIMER0_OVF_vect) {
        // this interrupt will happen every 1 ms
        millSec++;
        // Clear timer0 counter
        TCNT0 = 130;
    }


1 commentaires

Merci pour la réponse! Votre code a aidé à apprendre Timer0! J'ai également étudié la construction de Timer0 à Arduino Core et ça marche. Code - Pastebin.com/8RT10ARU



1
votes

Désolé, je suis en retard mais j'ai quelques suggestions. Si vous calculez la minuterie0 avec PRESCALER 1, la minuterie comporte chaque 125NS. Il n'est pas possible d'atteindre 1 US sans petite divergence. Mais si vous utilisez Prescaler 8, vous atteignez exactement 1 US. En fait, je n'ai pas votre matériel mais donnez-le à essayer:

int main(void)
{ 
  // Reset clock prescaling
  CLKPR = (1<<CLKPR);
  CLKPR = 0x00;
  // ...


0 commentaires