Moteur PWM: TCCR2A,TCCR2B, OCR2A, OCR2B

Bonjour, dans un sujet précédent je cherchais à faire fonctionner un ventilateur avec fonction PWM, j'ai donc utilisé ce code

void setup()
{
  // configure hardware timer2 to generate a fast PWM on OC2B (Arduino digital pin 3)
  // set pin high on overflow, clear on compare match with OCR2B
  TCCR2A = 0x23;
  TCCR2B = 0x0C;  // select timer2 clock as 16 MHz I/O clock / 64 = 250 kHz
  OCR2A = 249;  // top/overflow value is 249 => produces a 1000 Hz PWM
  pinMode(3, OUTPUT);  // enable the PWM output (you now have a PWM signal on digital pin 3)
  OCR2B = 125;  // set the PWM to 50% duty cycle
}

Cependant je souhaites ajouter un deuxième ventilateur PWM, et malgré avoir regardé dans la datasheet du µc je ne sais pas comment modifier les paramètres TCCR2A,TCCR2B, OCR2A et OCR2B.
J'aimerais donc comprendre le fonctionnement logique de celle-ci,afin de l'adapter pour la broche 5 (au lieu de la 3).

Merci

Bonjour,

ProgramFiles:
J'aimerais donc comprendre le fonctionnement logique de celle-ci,afin de l'adapter pour la broche 5 (au lieu de la 3).

C'est impossible, tu utilises le générateur de signaux PWM hardware du timer 2 canal B (OC2B).
Ce générateur est câblé en dure sur le broche D3 (broche PC3 en réalité).

Si tu veut avoir un signal PWM sur une autre broche il te faut utiliser une interruption sur "match compare B" du timer 2 et désactiver la liaison OC2B <-> pin 3.
Pour cela il faut aller jouer avec les registres du timer 2 et l'ISR qui va bien (voir datasheet et la doc de l'avr-libc sur <avr/interrupts.h>)

Merci de cette réponse, cependant pourais-je avoir un peu plus d'explication sur le procédé ?
J'ai pas l'habitude des datasheets et j'ai très peu de connaissance en éléctronique.

Quels sont les contraintes d'utiliser une autre broche, qui d'après ta réponse ne sera pas généré depuis le générateur de signaux PWM hardware ?

Merci

ProgramFiles:
Merci de cette réponse, cependant pourais-je avoir un peu plus d'explication sur le procédé ?
J'ai pas l'habitude des datasheets et j'ai très peu de connaissance en éléctronique.

Datasheet §18.11.1 :
Registre TCCR2A (configuration du timer 2 partie A) :

COM2A1 COM2A0 COM2B1 COM2B0 – – WGM21 WGM20

COM2A1 COM2A0 -> Gestion du générateur de PWM hardware de la broche OC2A
COM2B1 COM2B0 -> Gestion du générateur de PWM hardware de la broche OC2B
WGM21 WGM20 -> Gestion du mode de fonctionnement du générateur de PWM hardware OC2A et OC2B

Dans ton cas si tu veut un signal PWM logiciel :
COM2A1 = 0, COM2A0 = 0 -> pas de signal sur OC2A
COM2B1 = 0, COM2B0 = 0 -> pas de signal sur OC2B
WGM21 = 0, WGM20 = 0 -> Mode normal

Datasheet §18.11.2 :
Registre TCCR2B (configuration du timer 2 partie B) :

FOC2A FOC2B – – WGM22 CS22 CS21 CS20

FOC2A FOC2B -> "Force compare", j'ai jamais eu à utiliser ces bits ...
WGM22 -> suite de WGM21 et WGM20 du registre TCCR2A
CS22 CS21 CS20 -> choix du prescaler du signal d'horloge qui alimente le timer

Dans ton cas si tu veut un signal PWM logiciel :
FOC2A = 0, FOC2B = 0 -> "force compare" désactivé
WGM22 = 0 -> mode normal
CS22, CS21, CS20 -> à toi de calculer ton prescaler suivant la fréquence voulu
D'aprés la valeur de ton 1er post tu as CLK/64 => CS22 = 1, CS21 = 0, CS20 = 0

Registre TCNT2 -> compteur du timer 2
Met le à 0 avant de lancer le timer2

Registre OCR2A -> valeur de "match compare" du comparateur (canal) A
-> tu met ici la valeur de ton signal PWM
Registre OCR2B -> valeur de "match compare" du comparateur (canal) B

Registre TIMSK2 (gestion des interruptions du timer 2) :

– – – – – OCIE2B OCIE2A TOIE2

OCIE2A -> interruption sur "match compare" du comparateur (canal) A
OCIE2B -> interruption sur "match compare" du comparateur (canal) B
TOIE2 -> interruption sur "overflow" du compteur du timer 2

Dans ton cas si tu veut un signal PWM logiciel :
OCIE2A = 1 -> interruption lors du "match compare" de TCNT2 et OCR2A
OCIE2B = 0 -> pas d'interruption lors du "match compare" de TCNT2 et OCR2B
TOIE2 = 1 -> interruption sur overflow du timer

Ensuite tu doit utiliser :

// Cette 1er interruption place la broche à HIGH
ISR(TIMER2_OVF_vect) { // Timer 2 oveflow
  PORTn |= (1 << PINm); // n = lettre du PORT de la broche voulu, m = numéro de la broche voulu
}

// Cette 2eme interruption place la broche à LOW
ISR(TIMER2_COMPA_vect) { // Timer 2 OCR2A compare match
  PORTn &= ~(1 << PINm); // n = lettre du PORT de la broche voulu, m = numéro de la broche voulu
}

// En jouant avec la valeur de OCR2A tu modifie le duty de ton signal pwm
// Lors de l'overflow : signal PWM = HIGH
// Tant que TCNT2 != OCR2A : signal PWM = HIGH (inchangé)
// Lors du compare match : signal PWM = LOW
// Overflow -> attente -> compare match -> attente -> overflow -> ...

Dans setup() :
Avant de configuré le timer 2 :

cli(); // Désactive les interruptions

Aprés avoir configuré le timer 2 :

sei(); // Active les interruptions

ProgramFiles:
Quels sont les contraintes d'utiliser une autre broche, qui d'après ta réponse ne sera pas généré depuis le générateur de signaux PWM hardware ?

Le générateur de PWM hardware ne demande aucune utilisation du CPU.
La génération de signal PWM par interruption met en pause le CPU à chaque fois que l'interruption du timer se déclenche.
Si ton signal PWM à générer est de fréquence élevé tu va utiliser tout ton temps cpu à générer le signal PWM, le reste de ton code sera alors exécuté au ralenti.

Ah c'est vraiment dommage, mais pourquoi ne pas utiliser directement la fonction analogwrite ? Pourquoi il y a beaucoup de sortie indiqué PWM alors qu'une seul est seulement valable ?
Je pense que je vais utiliser ce montage

ça fonctionne sur des ventilateurs PC, il suffit de changer l'alimentation par du 12v (la temperature du transistor est à vérifier cependant).

Si j'ai bien compris, tu voudrais deux sorties Hardware PWM?

pour moi, c'est hyper simple, il suffit de faire tourner le timer2 en fast PWM avec TOP = 0xFF (255), et "connecter" les sorties OC2A et OC2B via les COM2A et COM2B en PWM... Ensuite, tu inscris la valeur de chacune de tes sorties via OCR2A et OCR2B.

// PWM A sur sortie OC2A - pin 11
// PWM B sur sortie OC2B - pin 3

void PWM_setup( boolean motA, boolean motB){
  TCCR2A = 0x03;  // Fast PWM, TOP = 0xFF
  if (motA){  // on initialise OC2A (pin 11)
    TCCR2A += 0x80; // Fast PWM sur OC2A
    OCR2A = 0;  // PWM A = 0
  }
  if (motB){  // on initialise OC2B (pin 3)
    TCCR2A += 0x20; // Fast PWM sur OC2B
    OCR2B = 0;  // PWM B = 0
  }
//  TCCR2B = 0x01;  // fréquence PWM = 16000000/1/256 = 62.5KHz ou
  TCCR2B = 0x02;  // fréquence PWM = 16000000/8/256 = 7.81KHz ou
//  TCCR2B = 0x03;  // fréquence PWM = 16000000/32/256 = 1.953KHz ou
//  TCCR2B = 0x04;  // fréquence PWM = 16000000/64/256 = 976Hz (au choix)

}

void PWM_put(byte canal, byte value){
  // envoie la valeur "value" (0-255) sur le canal "canal"(0 = A, 1 = B)

  if (canal) {  // canal b
    OCR2B = value;
  } else {  // canal A
    OCR2A = value;
  }
}

void setup(){
  PWM_setup(true, true);  // initialise les deux sorties PWM
}

void loop(){
  byte i;

  i++;
  PWM_put (0, i);
  PWM_put (1, 255-i);
  delay(100);  // change de valeur toutes les 100ms
}

Ce code va te faire tourner les deux moteurs en rampe. le moteur connecté à la pin 11 va démarrer à 0 et monter dans les tours, le moteur sur la pin 3 va démarrer à donf et descendre progressivement. La boucle dure 26 secondes environ.

Dans PWM_setup(), tu choisis l'une des quatre lignes concernant TCCR2B selon la fréquence de PWM que tu trouve la mieux adaptée (selon le bruit que feront tes moteurs, surtout, je pense que la première ligne à 62.5K est trop rapide)...

C'est un code généraliste, car si tu n'as besoin que d'une PWM, il te suffit de faire PWM_setup(true, false); ou PWM_setup(false, true); au niveau du setup() pour activer la PWM sur la pin 11 ou 3 respectivement. Attention par la suite à indiquer le bon canal dans PWM_put(canal, value);...

EDIT :
Ma méthode est certes la plus sûre et la plus directe (comment j'me la pète...), mais elle a l'inconvéniant de te restreindre grave sur le choix des pins. Une PWM logicielle est risquée (selon les interruptions qui peuvent arriver, cela peut déstabiliser la PWM), et bouffe du temps de traitement...

ProgramFiles:
Ah c'est vraiment dommage, mais pourquoi ne pas utiliser directement la fonction analogwrite ? Pourquoi il y a beaucoup de sortie indiqué PWM alors qu'une seul est seulement valable ?

La fonction analogWrite utilise les broches hardware qui sont annotés avec un ~ sur ta carte.
Tu as 3 timers dans l'ATmega328p, 2 broches OCxx par timer = 6 broches PWM au total (d'où les 6 ~ sur la carte).
Si tu fait un analogWrite() sur un broche non PWM (sans ~) ça revient à faire un digitalWrite().

Super_Cinci:
Si j'ai bien compris, tu voudrais deux sorties Hardware PWM?

J'ai cru comprendre qu'il voulais utiliser D5 comme broche pour la PWM (?)

Je veux utiliser la broche 5 (ou une autre) indiqué par un ~ sur la carte arduino UNO, cependant je ne peux pas me permettre de ralentir le temps d'execution du programme, la vrai contrainte est d'utiliser deux sorties PWM sans ralentir le programme.

Salut !

Il y a 3 timer dans l'arduino (Timer0, TImer1, Timer2), dans ton exemple tu utilise le timer2 (TCCR2A, OC2A, OCR2A,TCCR2B)

Les sorties HARD du timer 2 sont les pins 11 & 3 de l'arduino (11 pour OC2A & 3 pour OC2B)

J'ai 2 conseils pour toi :

  • Lire la datasheet
  • Re-lire la datasheet :wink:
    Surtout les chapitres 14, 15 & 17 (le 17 traitant particulièrement du timer2)

Je me suis fais un memo sur les timer, si celui-ci peut t'être utile :
Mon MEMO sur les TIMER

La sortie PWM de la pin 5 de l'Arduino que tu souhaite utiliser est OC0B, c'est à dire la sortie B du timer0... tu peux l'utiliser mais le timer0 est aussi utilisé par les fonctions Milis() & Micros().

UniseV

PS: Pourquoi la plupart des codes utilisant les timer sont paramétré en Hexa ??? je trouve ça illisible, je préfère le faire en binaire sur une seule ligne...

UniseV:
La sortie PWM de la pin 5 de l'Arduino que tu souhaite utiliser est OC0B, c'est à dire la sortie B du timer0... tu peux l'utiliser mais le timer0 est aussi utilisé par les fonctions Milis() & Micros().

Ralala quel boulet je fait, la broche D5 est une broche PWM, donc oui tu peut faire de la PWM hardware (avec analogWrite() par exemple).

Par contre cette broche PWM est "liée" au timer 0 qui gère les fonctions de temps (millis(), micros(), delay(), ...) si tu touche au prescaler et/ou aux interruptions du timer 0 tu va foutre en l'air tout ce qui est lié au temps "en arduino".

Donc si les 490Hz de base de analogWrite() te suffise c'est bon, sinon il faudra oublier millis(), ... (ou faire ta propre implémentation suivant la fréquence effective du timer)

UniseV:
PS: Pourquoi la plupart des codes utilisant les timer sont paramétré en Hexa ??? je trouve ça illisible, je préfère le faire en binaire sur une seule ligne...

Parce que la plupart des dév qui font du mix arduino/avr-c n'ont aucune idée de ce que _BV(bit) veut dire :wink:
Ps: le binaire c'est tout aussi illisible que de l'hexa et tout aussi dangereux :wink:

Exemple "propre" de manipulation de registre :

TIMSK2 = _BV(OCIE2B) | _BV(TOIE2);

Ou mieux sans toucher au reste du registre (si il y avait d'autres bits "utile") :

TIMSK2 = (TIMSK2 & ~_BV(OCIE2A))|_BV(OCIE2B)|_BV(TOIE2);

UniseV:
PS: Pourquoi la plupart des codes utilisant les timer sont paramétré en Hexa ??? je trouve ça illisible, je préfère le faire en binaire sur une seule ligne...

De mon côté, c'est parfaitement lisible, depuis le temps que je traine l'hexa, j'arrive à traduire du binaire vers l'hexa à la volée (1+2+4+8 = 0xF = 15, et d'entrée de jeu, je sais que 0x0C, ce sont les bits 3 et 2 de l'octet qui sont à 1 (8 + 4 = 0xc = 12...)). Le binaire est plus dur pour moi, sur 8 chiffres consécutifs, j'ai du mal à me repérer. Puis un "chiffre" hexa par bloc de 4 bits c'est très logique, même avec des registres 16 ou 32 bits ou même 64 bits, ça ne change rien pour moi... L'octal serait pas mal, mais un chiffre pour 3 bits, ça ne passe pas bien sur un octet... l'exemple d'un 16 bits : 1001110100111010 = 0x9D3A, je m'y retrouve bien mieux qu'avec 40250 (décimal), et voir le bit 7 su le binaire, je vois pas trop, mais dans ma tête, le bit 7 (en partant de 0 bien sûr) est le MSB (le bit qui a la plus grande valeur) du second hexa (en partant de la droite, le '3'), je sais tout de suite qu'il est à 0. de même, il vaut 0x80, et chez moi c'est instinctif car j'ai été "élevé" ainsi par mes profs...

skywodd:
Ou mieux sans toucher au reste du registre (si il y avait d'autres bits "utile") :

TIMSK2 = (TIMSK2 & ~_BV(OCIE2A))|_BV(OCIE2B)|_BV(TOIE2);

Là, par exemple, je suis incapable de comprendre et lire sans passer par le papier-crayon + datasheet...

Chacun son truc!

ProgramFiles:
Je veux utiliser la broche 5 (ou une autre) indiqué par un ~ sur la carte arduino UNO, cependant je ne peux pas me permettre de ralentir le temps d'execution du programme, la vrai contrainte est d'utiliser deux sorties PWM sans ralentir le programme.

Essaie mon code, mais tu es obligé d'utiliser les broches 11 et 3. Par contre, cette PWM est transparente niveau temps d'exécution. le changement d'une valeur de PWM ne coûte que la copie dune valeur 8 bits dans un registre, soit entre 1 et 3 cycles max (187ns) selon l'origine de la variable et tout le reste est géré matériellement sans aucune intervention du CPU (limite magique!).
On peut transposer le même code sur le timer1 pour jouer avec deux autres broches (OC1A et OC1B, pin 9 et 10). Ca a l'avantage de libérer entièrement le PORTD/PIND.
Jouer avec le timer0 peut se révéler délicat à cause qu'il y a déjà "du monde" dessus (micros(), millis(), delay()...). Le timer2 est le mieux, car ça permet de garder un timer 16 bits (donc jusqu'à 256 fois plus précis qu'un 8 bits) pour une autre tâche, on ne sait jamais.

S'il n'y a pas assez de commentaires, je t'en rajouterai...

Quand je dit la broche 5 c'est parce que ça m'arrange au niveau de la conception du PCB, mais ce n'est pas primordiale, le but final est de faire fonctionner deux moteurs PWM peut importe la broche tant que cela n'influence pas le temps d’exécution et que cela reste au plus simple.

Je déterre ce topic pour obtenir plus d'informations sur les timers, j'ai toujours besoin d'une solution pour faire fonctionner deux ventilateurs (de PC) avec contrôle de vitesse PWM.
Le premier utilise la broche 3 de l'arduino + le code fournis en début de topic, cependant le deuxième je ne sais pas quel broche utiliser n'y quel code.
Je pensais utiliser la broche 5 de l'arduino, cependant si j'ai bien compris, le faite de modifier depuis le registre celle-ci enlèverait les possibilités d'utiliser le temps (delay,etc...).
Donc j'aimerais connaitre quel timer entre les 2 restant serait le plus intéressant.
Sinon voici quelques informations concernant le PWM utilisé par les ventilateurs de PC:

Fréquence: 25Khz dans l'optimal, sinon de 21Khz à 28Khz
Maximum voltage for logic low: VIL= 0.8v
Absolute maximum current sourced: Imad = 5mA
Absolute maximum voltage level: VMax = 5.25V

Merci