Accélération de la minuterie AVR sur ATmega328

9

Lors de l'exécution à un horodateur de 64 sur ATmega328, l'un de mes temporisateurs accélère pour des raisons inconnues à un moment particulier de l'exécution.

J'utilise deux minuteries sur ATmega328 pour générer la synchronisation nécessaire au TLC5940 (voir ci-dessous pourquoi; cela n'a pas d'importance pour la question). TIMER0génère un signal d'horloge en utilisant Fast PWM on OC0Bet est configuré comme suit:

TCCR0A = 0
    |(0<<COM0A1)    // Bits 7:6 – COM0A1:0: Compare Match Output A Mode
    |(0<<COM0A0)    // 
    |(1<<COM0B1)    // Bits 5:4 – COM0B1:0: Compare Match Output B Mode
    |(0<<COM0B0)
    |(1<<WGM01)     // Bits 1:0 – WGM01:0: Waveform Generation Mode
    |(1<<WGM00)
    ;
TCCR0B = 0
    |(0<<FOC0A)     // Force Output Compare A
    |(0<<FOC0B)     // Force Output Compare B
    |(1<<WGM02)     // Bit 3 – WGM02: Waveform Generation Mode
    |(0<<CS02)      // Bits 2:0 – CS02:0: Clock Select
    |(1<<CS01)
    |(0<<CS00)      // 010 = clock/8
    ;
OCR0A = 8;
OCR0B = 4;
TIMSK0 = 0;

TIMER2tourne une ligne de données pour générer une impulsion de suppression tous les 256 TIMER0cycles et est configurée comme suit:

ASSR = 0;
TCCR2A = 0
    |(0<<COM2A1)    // Bits 7:6 – COM0A1:0: Compare Match Output A Mode
    |(0<<COM2A0)    // 
    |(0<<COM2B1)    // Bits 5:4 – COM0B1:0: Compare Match Output B Mode
    |(0<<COM2B0)
    |(0<<WGM21)     // Bits 1:0 – WGM01:0: Waveform Generation Mode
    |(0<<WGM20)
    ;
TCCR2B = 0
    |(0<<FOC2A)     // Force Output Compare A
    |(0<<FOC2B)     // Force Output Compare B
    |(0<<WGM22)     // Bit 3 – WGM02: Waveform Generation Mode
    |(1<<CS22)      // Bits 2:0 – CS02:0: Clock Select
    |(0<<CS21)
    |(0<<CS20)      // 100 = 64
    ;
OCR2A = 255;
OCR2B = 255;
TIMSK2 = 0
    |(1<<TOIE2);    // Timer/Counter0 Overflow Interrupt Enable

TIMER2appelle un ISR en cas de débordement (tous les 256 cycles). L'ISR génère manuellement une impulsion de suppression et une impulsion de verrouillage si nécessaire:

volatile uint8_t fLatch;

ISR(TIMER2_OVF_vect) {
    if (fLatch) {
        fLatch = 0;
        TLC5940_XLAT_PORT |=  (1<<TLC5940_XLAT_BIT);        // XLAT -> high
        for (int i=0;i<10;i++)
            nop();
        TLC5940_XLAT_PORT &= ~(1<<TLC5940_XLAT_BIT);        // XLAT -> high
    }
    // Blank
    TLC5940_BLANK_PORT |= (1<<TLC5940_BLANK_BIT);
    for (int i=0;i<10;i++)
        nop();
    TLC5940_BLANK_PORT &= ~(1<<TLC5940_BLANK_BIT);
}

Le nop()retard dans le code ci-dessus est juste pour rendre l'impulsion plus apparente sur la trace de l'analyseur logique. Voici à quoi ressemble la boucle de la main()fonction: envoyez des données série, attendez qu'ISR s'occupe du verrouillage, puis recommencez:

for (;;) {
    if (!fLatch) {
        sendSerial();
        fLatch = 1;
        _delay_ms(1);
    }
    nop();
}

sendSerial()fait des envois SPI ( code sur pastebin par souci de concision ). Mon problème est qu'après la sendSerial()fin, en attendant fLatchqu'il soit réglé sur faible (traité), le chronomètre accélère. Voici la trace de l'analyseur logique (j'ai découpé les zones où le même signal continue de rendre le graphique plus petit):

entrez la description de l'image ici

Sur le côté gauche, les canaux 0 et 1 montrent l'extrémité arrière des données SPI envoyées. Également à gauche, sur le canal 4, vous pouvez voir une impulsion de suppression. Sur le canal 2, l'impulsion d'horloge avance comme prévu. Juste là où se trouve l'écart dans l'image, il fLatchest réglé à l' 1intérieur de la main()routine. Et peu de temps après, TIMER0accélère d'environ un facteur 4. Finalement, l'impulsion de suppression et l'impulsion de verrouillage sont effectuées (canaux 3 et 4, tiers droit de l'image), et maintenant l'impulsion de synchronisation reprend sa fréquence régulière, et les données série sont envoyé encore. J'ai essayé de retirer la delay_ms(1);ligne main(), mais les mêmes résultats sont obtenus. Que se passe-t-il? Je dois noter que l'ATmega est cadencé sur un cristal de 20 MHz, puis ralenti de 64 fois à l'aide du code suivant:

CLKPR = 1<<CLKPCE;
CLKPR = (0<<CLKPS3)|(1<<CLKPS2)|(1<<CLKPS1)|(0<<CLKPS0);

À quoi cela sert-il: J'expérimente le contrôle du pilote LED TLC5940 : ces puces nécessitent une horloge externe plus une réinitialisation à la fin du cycle de synchronisation.

angelatlarge
la source
Si vous avez un débogueur, essayez d'arrêter votre code lorsque le minuteur est trop rapide et relisez le registre de configuration de ce minuteur. Une fois que vous avez trouvé celui avec la mauvaise valeur, déclenchez un point d'arrêt sur ce changement de registre et voyez quelle partie de votre code fonctionne mal. Je suppose que le problème se trouve dans une bibliothèque externe que vous pouvez utiliser et qui utilise ce minuteur pour des éléments internes tels que les retards.
Blup1980
Deux problèmes: a) Je n'ai pas de programmeur JTAG, donc je n'ai pas de moyen de déboguer la puce b) Je ne change jamais la valeur du registre du minuteur après la configuration ci-dessus, donc je ne m'attends pas à ce que les valeurs du registre du minuteur changer réellement. C'est naïf?
angelatlarge
1
En fait, une bibliothèque que vous utilisez pourrait modifier les paramètres UART. Je vois que vous utilisez une fonction sendSerial (). Fait-il partie de votre code ou s'agit-il d'une bibliothèque externe? Ce n'est peut-être pas vous qui modifiez les paramètres, mais un morceau de code dans une bibliothèque appelée. Je vous suggère d'utiliser votre port série pour sortir les paramètres de configuration et essayer de comprendre ce qui a changé. Vous pouvez également consulter la source des bibliothèques utilisées (le cas échéant) et vous assurer qu'elles n'utilisent pas également ce minuteur.
Blup1980
1
En dehors de ce que @ Blup1980 a suggéré, une autre chose qui pourrait valoir la peine d'être essayée est de retirer le TLC5940 pour vous assurer qu'il ne fait rien de bizarre avec la ligne d'horloge.
PeterJ
@ Blup1980 Je ne suis pas sûr de voir la pertinence de l'UART: je n'utilise pas USART pour SPI, juste les installations SPI "régulières". sendSerial()est mon code qui envoie des données via SPI: il ne touche pas aux TCCRregistres (contrôle de la minuterie).
angelatlarge

Réponses:

1

Pour un débogage rapide, j'essaierais de faire la même chose en utilisant la bibliothèque Arduino pour TLC5940 et de voir si cela devient rapide ou non. Si cela fonctionne avec la bibliothèque, vous pouvez vérifier sa source et comparer avec la vôtre. Puisque vous êtes familier avec AVR, vous devriez facilement convertir la source Arduino en AVR natif.

Juste au cas où vous ne savez pas comment télécharger des croquis Arduino compilés sur AVR: lorsque vous compilez votre croquis, il crée un fichier hexadécimal (vous pouvez voir l'emplacement exact du fichier en activant le mode prolixe dans les paramètres). Vous pouvez télécharger cet hex sur votre AVR avec votre programmeur préféré.

J'espère que cela aide

superkeci
la source