Générer un PWM à 25kHz

Bonjour

J'utilise deux ventilateurs de PC pour refroidir un système audio de puissance. Je me sers d'une carte mega2560. Le système de refroidissement est asservi à la température du système à refroidir. Le pilotage par PWM de la vitesse des ventilateurs est une solution parfaitement adaptée.
Les ventilateurs courants fonctionnent avec un signal PWM de 25kHz (avec une certaine tolérance). Je dois donc modifier les paramètres du timer du µC pour viser cette fréquence.
Après quelques recherches sur le Web, et une lecture par toujours très compréhensibles pour moi de la datasheet de l'atmega2560, je suis parvenu à un résultat pas tout à fait satisfaisant.
D'autre part, j'avoue avoir tatonné dans les registres du timer, en m'inspirant des exemples que j'ai trouvé sur le net, sans toujours bien comprendre ce que je faisais.
J'aimerai si possible avoir des explications sur ce que j'ai fait, et aussi corriger les erreurs, car il doit y en avoir quelques unes.
Tout d'abord l'erreur en question: Tout fonctionne à peu près bien, la fréquence est bien d'environ 25kHz (vérifié à l'oscillo), le rapport cyclique généré est bien respecté (10% en envoyant la valeur 32, 50% avec 160, 100% avec 320), mais le rapport cyclique est également à 100% lorsque j'envoie la valeur 255 (tel qu'il est illustré dans le code ci dessus)
Ensuite, je n'ai pas l'habitude de programmer les registres avec ce type de code. Par exemple, pour paramétrer TCCRA5, j'aurai plutot fait: TCCR5A=b00100000|b00000010, mais je ne sais pas si c'est bien correct...
Voici le code que j'ai créé spécifiquement pour cet usage. Le signal PWM est envoyé par la pin45 (OC5B de la puce).


```cpp

const int ventR = 45;
void setup() {
  TCCR5A = 0;
  TCCR5B = 0;
  TCNT5 = 0;

  // Mode 10: phase correct PWM with ICR5 as Top (= F_CPU/2/25000 =320)
  // OC5C as Non-Inverted PWM output
  ICR5 = (F_CPU / 25000) / 2;
  OCR5B = 0;  // rapport cyclique au démarrage
  TCCR5A = _BV(COM5B1) | _BV(WGM51);
  TCCR5B = _BV(WGM53) | _BV(CS50);

  pinMode(ventR, OUTPUT);
}

void loop() {
  analogWrite(ventR, 255);
}

un bon article pour comprendre comment ça fonctionne:

https://docs.arduino.cc/tutorials/generic/secrets-of-arduino-pwm

et sinon il y a une bibliothèque qui peut faire les calculs pour vous

Bonsoir
a lire ..

Bonjour

Pas mal de lectures intéressantes, je vais prendre le temps d'étudier tout çà.

Je vois cependant que RitonDuino ne s'embarrasse pas, il utilise une fréquence standard. Si çà fonctionne comme tel, c'est encore plus simple. (je vais tester de ce pas)
Mais je voudrais quand même résoudre ce petit exercice de codage, cela me permets d'apprendre beaucoup de chose.

Petite question bête que je n'ai pas osé posé tout de suite. Lorsqu'on code:
TCCR5A = _BV(COM5B1) | _BV(WGM51)

celà signifie t il qu'on active (prend la valeur 1) le bit correspondant à COM5B1, et (ou?) idem pour le bit correspondant à WGM51 du registre TCCR5A? Et les 6 autres bit sont de facto à 0? (le registre comporte 8 bit)

Bonsoir @Geosop

il me semble qu'avec cette opération les 6 autres bits du registre sont inchangés , ils gardent leur valeur antérieure..... éventuellement une valeur définie au Reset de la puce, voir la Data Sheet pour l'état initial du registre en question

ll serait possible de mettre ces bits à 0 avec un masque en ET dans le même esprit que le masque en OU utilisé ici pour mettre certains bits à 1

Bonjour

Merci pour cette précision, c'est bien ce qui me semblait. Mais encore plus bête question, pourquoi mettre un masque "OU"? On impose pas la valeur des bit? c'est le processeur qui choisit selon son humeur? (vous voyez mon niveau...)

voir la table de vérité de la fonction logique OU entre 2 bits

en faisant un OU entre un bit d'état quelconque et un autre à 1 , le résultat de l'opération est nécessairement 1

BV(COM5B1) représente un 1 'positionné' au bon endroit par un décalage approprié , sa définition se trouve quelque part dans un fichier

L'effet sur le bit ciblé est déterministe, pas aléatoire !texte en italique

Non, ici on fait une affectation
Cette ligne

est équivalente à

TCCR5A = (1 << COM5B1) | (1 << WGM51)

donc tous les bits du registre sont écrasés par l'octet construit dans la partie droite du signe =.

Pour que les autres bits soient inchangés, il faudrait faire

TCCR5A |= (1 << COM5B1) | (1 << WGM51)
ou
TCCR5A = TCCR5A | (1 << COM5B1) | (1 << WGM51)

Merci @fdufnews pour cette rectification !

pour clarifier peut être encore plus

  • COM5B1 est le 5e bit, soit le bit 4. ➜ _BV(COM5B1) met le bit 4 à 1 : 0b00010000
  • WGM51 est le 1er bit, soit le bit 0 ➜ _BV(WGM51) met le bit 0 à 1 : 0b00000001

En combinant les deux en utilisant l'opérateur binaire OR (|), vous obtenez la valeur binaire finale pour la configuration : 0b00010001.

donc l'écriture TCCR5A = _BV(COM5B1) | _BV(WGM51); dit au compilateur de faire

TCCR5A = 0b00010001;

ce qui force les bits à 1 et 0 dans TCCR5A

Le bit 5 configure le mode de sortie PWM sur la broche OC5B pour générer un signal PWM non inversé et le bit 1 configure le mode de fonctionnement du Timer/Counter 5 en mode de comparaison (CTC pour Clear Timer on Compare Match).

➜ Dans ce mode, le compteur TC5 compte jusqu'à une valeur spécifiée par le registre de comparaison OCR5A, puis il est effacé automatiquement lorsque cette valeur est atteinte, ce qui permet de générer des signaux PWM précis

Voici le début du code pour analogWrite:

void analogWrite(uint8_t pin, int val)
{
	// We need to make sure the PWM output is enabled for those pins
	// that support it, as we turn it off when digitally reading or
	// writing with them.  Also, make sure the pin is in output mode
	// for consistenty with Wiring, which doesn't require a pinMode
	// call for the analog output pins.
	pinMode(pin, OUTPUT);
	if (val == 0)
	{
		digitalWrite(pin, LOW);
	}
	else if (val == 255)
	{
		digitalWrite(pin, HIGH);
	}
	else

En définitive analogWrite(255); appelle directement digitalWrite(pin, HIGH); qui a pour effet de passer la broche à HIGH et donc être équivalent à un rapport cyclique de 100%. C'est en fait un cas particulier.

Je n'ai pas vérifié, mais si tu envoie une valeur différente de 0 et de 255, le code suivant va être exécuté:

			#if defined(TCCR5A) && defined(COM5B1)
			case TIMER5B:
				// connect pwm to pin on timer 5, channel B
				sbi(TCCR5A, COM5B1);
				OCR5B = val; // set pwm duty
				break;
			#endif

En gros il met la valeur (8 bits) dans le registre de comparaison OCR5B. Cela fonctionne d'habitude car par défaut le PWM est censé fonctionner avec les 8 bit de poids faibles. En utilisant une fréquence de 25kHz, et en comptant jusqu'à 320, cela ne fonctionne plus.

Tu dois avoir:
10% en envoyant la valeur 32
50% avec 160
79% avec 254
100% avec 320
79% avec analogWrite(254) car on envoie 254 dans OCR5B
100% avec analogWrite(255) car on envoir digitalWrite(45,HIGH)
10% avec analogWrite(32) car on envoie 32 dans OCR5B

analogWrite permet de faire alors varier le rapport cyclique de 0% à 79%

Conclusion: si la fréquence est telle que le maximum n'est plus 256; il faut utiliser
OCR5B=rapportCyclique320/256;
au lieu de
analogWrite(45, rapportCyclique
320/256);

Bonjour à tous

Alors merci pour vos réponses, cela m'éclaire beaucoup, je comprends bien mieux le code utilisé pour paramétrer les registres, et règle le problème de la valeur particulière du 255 (je n'aurai jamais trouvé tout seul cette histoire de analogWrite!)

Bon week end!

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.