Question:
Aide à gérer les retombées du mode timer0 et le changement de prescaler sur ATMega2560 sans modifier le câblage.c
Nerbsie
2016-10-13 19:45:46 UTC
view on stackexchange narkive permalink

Pour une application de conduite, j'ai besoin que le mode PWM soit correct en phase et que le prédécaleur soit 1. Le problème est que la carte avec laquelle je travaille a la charge reliée à une broche PWM dépendant de timer0. Le firmware fonctionnant sur cette carte utilise les fonctions delay () , delayMicroseconds () et millis () du câblage.c. Le but est de faire le changement de timer0, illustré ci-dessous, puis d'apporter de petites modifications là où le micrologiciel appelle les fonctions de synchronisation de câblage.c. Mon approche a été de trouver un facteur d'échelle à appliquer à l'appel de ces fonctions. Le facteur de mise à l'échelle auquel je suis arrivé était de 32,12549 , calculé en prenant le comportement du timer0 attendu de câblage.c: prescaler = 64 et TOV0 drapeau tous les 256 comptes , et en le comparant aux valeurs réelles après ma modification: prescaler = 1 et TOV0 marquent tous les 510 points

Calcul: 64 * 256/510 = 32.12549

Changement de Timer0:

  // Réglez timer0 sur la phase correcte PWMTCCR0A = TCCR0A & 0b11111100 | 0x01; // Définit le prescaler pour timer0 sur 1TCCR0B = TCCR0B & 0b11111000 | 0x01;  

Les autres temporisateurs sont tous configurés en mode PWM correct de phase, avec un prescaler de 1.

... Quand j'applique ce facteur de mise à l'échelle à une valeur retourné de millis () pour garder le temps ça marche bien. Par exemple, les deux morceaux de code ci-dessous se sont avérés équivalents grâce au changement de timer0:

Utilisation de millis () avant le changement de timer0:

  // current timelong unsigned now = millis (); // délai pendant 1 seconde while (millis () - now < 1000) {}  

Utiliser de millis () après le changement de timer0:

  // current timelong unsigned now = millis (); // délai de 1 seconde, avec x32. 125 coefficient de mise à l'échelle pendant (millis () - maintenant < 32125) {}  

Les deux extraits de code ci-dessus ont tous deux retardé pendant 1 seconde

Maintenant vient le problème: quand j'essaye d'appliquer ce même facteur de mise à l'échelle à delay () ou delayMicroseconds () il semble donner des délais déraisonnables ...

Utilisation de delayMicroseconds () avant le changement de timer0:

  // attendez 1,5 ms pour que le multiplexeur change de délaiMicrosecondes (1500);  

... cela retarde 1,5 ms

Utilisation de delayMicroseconds () après le changement de timer0:

  // attendez 1,5 ms pour que le multiplexage change, avec mise à l'échelle x32.125 coefficientdelayMicrosecondes (48188);  

... cela retarde d'environ 20 ms.

En supposant que cela reste linéairement évolutif: si 48188 -> 20ms, donc 1.5ms -> 3614

Mais quand le code suivant est utilisé ...

  // attendez 1,5 ms pour que le multiplexage change, scaleddelayMicrosecondes (3614);  

... cela retarde d'environ 4,5 ms

Pourquoi est-ce que delayMicroseconds () et delay () ne peuvent pas être mis à l'échelle linéairement pour tenir compte d'un changement de timer0? De plus, y a-t-il une modification simple de la façon dont ces fonctions sont appelées ou utilisées qui peuvent expliquer l'effet du changement de timer0?

Vous voudrez peut-être vérifier le [code source Arduino] (https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/wiring.c). Il semble que delayMicroseconds n'utilise même pas timer0. Votre code utilise-t-il des interruptions, car cela fausserait le résultat de delayMicroseconds? Avez-vous manqué de broches PWM sur le Mega, dont vous avez besoin pour utiliser timer0?
Oui, timer0 doit être utilisé ici. Le code utilise des interruptions, ce qui pourrait affecter cela (je n'avais pas remarqué que `delayMicroseconds ()` n'utilise pas `micros ()`, qui bloque les interruptions; je suppose que c'est le cas puisque `delay ()` le fait) - bien que j'utilise un oscilloscope pour mesurer la sortie (basculer une broche de chaque côté du retard) et que le retard soit répétable, alors que nos interruptions peuvent survenir à tout moment pendant le code. S'il s'agit d'une interruption à l'origine du problème, je m'attendrais à ce que le délai varie au fur et à mesure que la boucle s'exécute, ce qui n'est pas le cas. Je ferai de mon mieux pour lire le code source de `delayMicroseconds ()`
Je pense que vous avez raison en fait, et je pense que l'interruption qui cause cette erreur pourrait en fait être les interruptions de dépassement de la minuterie. En changeant les vitesses d'horloge (j'ai en fait augmenté la vitesse d'horloge de tous les minuteries PWM), j'ai provoqué le déclenchement de l'indicateur d'interruption TOVn de chaque horloges tous les 31,875us, par opposition à l'opération standard qui est tous les 1024us.
Un répondre:
Edgar Bonet
2016-10-14 14:59:13 UTC
view on stackexchange narkive permalink

Les millis () roulent

En accélérant le Timer 0, votre millis () roulera beaucoup plus souvent que d'habitude: toutes les 37,1 heures au lieu de tous les 49,7 jours. Ainsi, lorsque vous «mettez à l'échelle» millis () , vous devez faire attention à le faire de manière sécurisée. D'après le code que vous montrez, il semble que vous faites exactement la bonne chose . Bien joué! Ne change rien. Ne soyez pas tenté de mettre à l'échelle la valeur retournée par millis().

Le débordement en delayMicrosecondes ()

Comme cette fonction ne fait que brûler des cycles dans un temps occupé boucle, il n'est pas affecté par votre configuration de Timer 0. Vous pouvez simplement l'utiliser tel quel, avec noscaling. Attention cependant, son argument doit toujours être inférieur à 16384 (sur une carte 16 MHz). Sinon, il débordera lorsqu'il est multiplié par 4 afin d'obtenir un nombre de boucles.

Par exemple, lorsque vous appelez delayMicroseconds (48188); , la fonction calcule le nombre d'itérations de boucle faire comme

48188 × 4 - 5 = 61675 (modulo 2 16 )

Ensuite, il brûle environ 15 ms de temps CPU (0,25 µs de péritération ), ce qui est proche des 20 ms que vous voyez. Pour la différence, voir ci-dessous.

Les cycles brûlés par le Timer 0 ISR

Cet ISR, qui maintient le compteur millis () à jour, est censé s'exécuter toutes les 1024 µs. Chaque fois qu'il s'exécute, il brûle quelques cycles, mais ce n'est généralement pas un gros problème. Avec votre nouvelle configuration de minuterie, il tourne maintenant beaucoup plus souvent: tous les 31,875 µs.

D'après les chiffres que vous donnez dans la question, je dirais que l'ISR consomme environ 20% de la puissance de votre processeur: lorsque vous appelez delayMicroseconds (3614); , cette fonction brûle 3614 µs de temps CPU, mais vous mesurez un retard d'environ 4500 µs. La différence est prise par le Timer 0 ISR.

Utilisez une autre minuterie pour millis ()

Si vous pouvez épargner une minuterie, vous pouvez à la fois restaurer le comportement normal de millis ( ) et éviter de gaspiller autant de CPU dans le Timer 0 ISR:

  • configurer Timer 0 pour ne pas déclencher d'interruptions
  • configurez votre minuterie de secours comme le cœur Arduino configure normalement le Timer 0
  • écrivez un ISR de débordement pour votre minuterie de rechange qui appelle simplement le Timer 0overflow ISR.

De cette façon, votre minuterie de secours remplace fondamentalement la minuterie 0. Exemple d'utilisation de la minuterie 2:

  // Cet ISR de débordement de la minuterie 2 transfère simplement l'appel au minuteur 0 // débordement ISR de Arduino core.ISR (TIMER0_OVF_vect); ISR (TIMER2_OVF_vect, ISR_NAKED) {TIMER0_OVF_vect (); asm volatile ("reti"); // Alternativement, juste: // asm volatile ("jmp __vector_16");} void setup () {// Définit timer0 pour corriger la phase PWM TCCR0A = (TCCR0A & 0b11111100) | 0x01; // Règle le prescaler pour timer0 sur 1 TCCR0B = (TCCR0B & 0b11111000) | 0x01; // Désactive les interruptions du minuteur 0. TIMSK0 = 0; // Configurez le temporisateur 2 pour remplacer le temporisateur 0. TCCR2A = _BV (WGM20) // PWM rapide, TOP = 0xff | _BV (WGM21); // idem TCCR2B = _BV (CS22); // horloge à F_CPU / 64 TIMSK2 = _BV (TOIE2); // active l'interruption de débordement pinMode (LED_BUILTIN, OUTPUT);} void loop () {// Clignote la LED à 1 Hz. uint32_t maintenant = millis (); static uint32_t last_toggle; if (now - last_toggle > = 500) {last_toggle + = 500; digitalWrite (LED_BUILTIN,! digitalRead (LED_BUILTIN)); }}  

J'ai écrit un ISR "nu" pour éviter d'avoir beaucoup de registres CPU sauvegardés et restaurés deux fois (une fois par chaque ISR dans la chaîne d'appels), mais ensuite J'avais toput l'instruction de retour explicitement dans l'assemblage ( ret et reti seraient équivalents dans cette instance). La version avec jmp est légèrement plus efficace, mais vous devez connaître le nom «réel» de l'ISR, qui dépend du MCU ( __vector_16 sur un Uno).

Notez qu'avec cette technique, micros () ne fonctionnera pas, car il lit directement TCNT0. Et delay () ne fonctionnera pas non plus car il repose sur micros().

Réponse fantastique, merci. Il semble que «delayMicroseconds ()» ne sera pas fiable, et en fonction de la longueur des retards que mon projet nécessite, je pense que se fier exclusivement à «millis ()» serait bien. Nous utilisons tous les minuteurs pour les sorties PWM à ce stade, et comme vous pouvez probablement le déduire, nous avons un microcontrôleur assez alourdi. Je pense que les autres minuteries sont 16 bits, donc elles seraient beaucoup moins fréquentes - je commence à penser que nous ne pouvons pas gérer l'accélération du timer0 tout en gérant les communications et les calculs de manière fiable.
Par intérêt, comment en êtes-vous arrivé à cette conclusion: "D'après les chiffres que vous donnez dans la question, je dirais que l'ISR consomme environ 20% de votre CPU"?
1) Comment avez-vous configuré les autres minuteries? Pourraient-ils fonctionner en mode PWM rapide à 977 Hz? Vous souhaiterez peut-être ajouter ces informations à la question. Arduino configure de toute façon la seule minuterie 16 bits en mode 8 bits. 2) Sur 4500 µs, 3614 sont dépensés à l'intérieur de «delayMicrosecondes (3614)». Le reste, à savoir 886 µs (soit 20%) est mangé par cet ISR.
Les choses changent au fur et à mesure que j'approfondis cela, et il semble que s'éloigner de timer0 soit suffisamment important pour mériter des modifications matérielles. En outre, j'utilise un ATMega2560, qui a 4x minuteries 16 bits (1,3,4,5) et 2x minuteries 8 bits (0,2). Je dois revenir en arrière dans le firmware et vérifier que nous les avons définis sur 16 bits (Arduino par défaut tous les minuteries sur 8 bits?), Mais il semble que vous m'ayez donné plus de raisons que de consacrer mes efforts à travailler loin de timer0.


Ce Q&R a été automatiquement traduit de la langue anglaise.Le contenu original est disponible sur stackexchange, que nous remercions pour la licence cc by-sa 3.0 sous laquelle il est distribué.
Loading...