ATtiny - PWM fréquence à 25khz et communication en esclave

Bonjour,

Je souhaite utiliser un ATtiny85 pour piloter des ventilateurs en PWM et par rapport à cela j' ai fait pleins de tests et lu tellement de choses sur internet que je suis complétement perdu pour comprendre comment le faire...
En finalité je me suis décidé à risquer d'écrire mon problème pour que je puisse avancer et ne plus me sentir comme le dernier des boulets...

l'inventaire à ma disposition!

  • Premièrement ma contrainte: j'ai des ventilateurs (Noctua en 5V PWM) et dans la datasheet de ceux-ci il est conseiller d'avoir un PWM à 25kHz pour les piloter (voir p.6 de Noctua White Paper ) et plus spécifiquement il est dit: "Target frequency: 25kHz, acceptable range 21kHz to 28kHz"

  • Ensuite j'ai un stock d'ATtiny85 que je souhaite utiliser. et en regardant le "Pinout" de ce composant j'ai constater qu'il à 4 pattes qui sont "taguées" comme étant capable de faire du PWM... super! c'est tip-top pour ce que je souhaite faire (me dis-je avant de désespérer faces aux obstacles...)

  • Et pour finir j'ai un arduino UNO que j'ai réussi a configurer pour programmer l'ATtiny85 (le Blink fonctionne très bien) et 1 Arduino nano qui doit me servir à piloter les ATtiny85.

  • Je me suis fait un petit programme pour faire mes tests de l'ATtiny85. voila à quoi il ressemble:

unsigned int uiTmpPause;
byte pinPWM[] = {0, 1, 3, 4}; //0=PB0=Pin5 //1=PB1=Pin6 //3=PB3=Pin2 //4=PB4=Pin3
byte pinLed = 2; //2=PB2=Pin7

void setup() {

//*********** test 1 ***********
//uiTmpPause = 130; 
//*** Clignotement de la led = ~1s
//*** PWM du PB0 à 16ms = 62,5Hz
//*** PWM du PB1 à 16ms = 62,5Hz
//*** PWM du PB3 à 2s   = 0,5Hz - pas de pwm - 50/50
//*** PWM du PB4 à 1ms  = 1KHz
//*********** test 1 - FIN ***********

//*********** test 2 ***********
  TCCR0A = TCCR0A & 0b11111000 | 0x01; // for PB0
  TCCR0B = TCCR0B & 0b11111000 | 0x01; // for PB1
  uiTmpPause = 10000;
//*** Clignotement de la led = ~1s
//*** PWM du PB0 à 500µs = 2kHz
//*** PWM du PB1 à 500µs = 2kHz
//*** PWM du PB3 à 2s   = 0,5Hz - pas de pwm - 50/50
//*** PWM du PB4 à 1ms  = 1KHz
//*********** test 2 - FIN ***********

  pinMode(pinLed, OUTPUT); //Pin Led

  //pinPWM[]
  for (int i = 0; i < 4; i = i + 1) {
    pinMode(pinPWM[i], OUTPUT); //pinPWM[]
  }
}

void loop() {
  static byte  bFlag;
  if (bFlag == 1) {
    bFlag=0; funBoucle (1, 50);} 
  else {bFlag=1; funBoucle (0, 200);}
}

void funBoucle (byte bLed, byte bPwm) {
  if (bLed ==1) digitalWrite(pinLed, HIGH); // Allume/eteind la LED
  else digitalWrite(pinLed, LOW);
  for (int i = 0; i < 4; i = i + 1) {
    analogWrite(pinPWM[i],bPwm); // PWM bPwm/256
  }
  delay(uiTmpPause); // Tempo
}

Au niveau du branchement de l'ATtiny j'ai juste un combo resistance/ led sur sa patte PB2=Pin7
et j'ai placé un oscillo sur les pattes PB0=Pin5, PB1=Pin6, PB3=Pin2 et PB4=Pin3

Mes questions:

  1. Est-il vrais que l'ATtiny85 à bien 4 port PWM comme l'indique la pinout? faut il paramétrer quelque chose pour les utiliser? ont elles des limites en fréquences? ou en période?

  2. Comment faire pour que les pattes utilisable au point 1) aient une fréquence de 25kHz au mieux ou par défaut entre 21 et 28kHz?
    A noter que j'ai vu plein d'exemple, que j'ai essayé de mettre en place, mais sans sucés. Je vous ai mis 2 tests avec les mesures à l'oscilloscope. Les résultats sont dans le code ci-dessus.
    Auriez vous donc un lien ou une explication pour arriver enfin à la fréquence requise? pliiiiiizzzzzee

  3. Est il possible de communiquer en finalité avec l'Arduino nano avec les pattes non utilisé de l'ATtiny (PB5=Pin1, PB2=Pin7) et cela dans le but faire varier le PWM des pattes utilisables.
    Si c'est possible auriez vous un exemple? quel bibliothèque utiliser?

Merci d'avance votre aide, même minime, et si au final on trouve une solution je promet de mettre tous le code ici pour qu'il puisse aider par la suite

Bonjour,
Je vous conseillerais la configuration suivante.(une piste parmi d'autres certainement)
Utiliser PB0 (5) et PB2 (7) pour une communication I2C -- l'attny85 en esclave.

puis PB1 (6) et PB4 (3) comme sortie pwm.
A vérifier si on peux fixer la fréquence aux alentour des 25kHz.

Oui et non. les broches PB3 à PB0 peuvent bien être utilisées en PWM MAIS dans ce cas PB0 est complémentaire de PB1 et PB3 est complémentaire de PB4. Si on programme un rapport cyclique de 10% sur PB0, et si PB1 à été programmé pour du PWM, on aura un rapport cyclique de 90%.
On a donc bien 4 broches qui peuvent faire du PWM, mais en pratique deux sont inutilisables

Forcément.
On peut faire du PWM hardware pur auquel cas on utilise un timer qui tourne seul et on le laisse gérer les signaux. A partir d'une fréquence (bien souvent celle du quartz), le timer compte sur 8 bits et de ce fait divise la fréquence par 256. En comparant la valeur de ce compteur avec un registre, on crée le PWM. Il y a donc 256 valeurs possibles pour le rapport cyclique.
La fréquence d'entrée du timer peut être celle du quartz, ou celle issu du diviseur par 8, 64, 256 ou 1024. Si la fréquence du quartz est de 1MHZ, la fréquence de PWM peut être:

  • 3,9kHz sans division
  • 488Hz si on divise par 8
  • ...
  • 3,8Hz si on divise par 1000
    Si au lieu d'u quartz à 1MHZ tu utilise un quartz à X MHz, il suffit de multiplier les chiffres par X. ( par exemple à 8MHz, on a 31kHz).
    On peut aussi utiliser une horloge externe à fréquence 256 fois plus grande, mais cette horloge est échantillonnée par l'horloge interne (celle du quartz).
    Idéalement pour avoir du PWM à 25kHz, il faudrait que l'Atiny85 tourne à 6,4MHz.
    La limite de fréquence du quartz est de 4 à 20MHZ suivant la tension d’alimentation et la version (85 ou 85V; V cela veut dire "comme un veau" soit deux fois plus lent).

La limite basse n'exista pas avec une horloge externe, mais c'est 3,8Hz pour un quartz à 1MHz. Pour les fréquences basses on peut passer par un PWM soft et ralentir autant que l'on veut.

Je vois deux solutions:

  • utiliser un quartz de 6,4, et programmer les registres directement. Il est possible que analogWrite remettre la fréquence à 1 ou 2kHz.
  • faire du PWM soft, maisà 25kHz il y a des chance pour être obligé de diminuer la résolution. C'est simple à faire, mais ce n'est pas très performant. Et se pose le problème du dialogue avec la Uno. Cela prend du temps machine et peut ralentir le PWM soft.

-> quelle résolution est souhaitée sur le PWM? (256 valeurs possibles, 100 valeurs possibles, 10 valeurs possibles?)
-> à quelle vitesse tourne l'Atiny85?
-> quel type de dialogue instaurer? Cela peut être une info pour augmenter le rapport cyclique, une info pour diminuer, ou une liaison SPI...

Bonjour Vileroi
çà fait un moment je n'ai pas vraiment joué avec les attiny
mais on peut choisir choisir la base du timer1 parmi ces 3 là

NB il y a plusieurs portages pour les attiny 85
image

Effectivement, tu as raison. Pour mon post de dessus, il faut le corriger en multipliant tout par 64 car on peut avoir l'horloge "asynchrone" à 64 kHz.

La solution est de faire tourner le timer à 4MHz (via l'horloge à 64MHz divisée par 16) et de compter jusqu'à 160 (OCR1C=159). Dans la doc du micro, est donné une table pour choisir la fréquence entre 20kHz et 500kHz. Le PWM en sortie variera de 0% (pour OC1A=0) à 100% (pour OC1A=160) ou l'inverse.

Merci à vous @Leptro , @vileroi et @Artouste pour vos réponses, il va maintenant falloir que je digère cela :wink:

@Leptro : Merci pour ton conseil, je vais faire des tests en I2C entre 1 voir 2 ou +ATtiny85 en esclave et d'un nano maître. ( Par exemple ce lien) Mon but sera, dans un premier temps d'arriver à allumer et éteindre une led branchée sur l'ATtiny85 et commandé par le nano...
De ce que je comprend donc c'est pas 4 pwm mais que 2. c'est déjà très bien!

=> des exemples j'en trouve plein, et ce que je comprend c'est que l'i2c et géré surtout de façon soft, et je trouve des réservation de pattes de l'ATtiny85 pour l'i2c différentes... j'en conclue qu'il n'y à pas de règle pour choisir les pattes?

@vileroi : Wahouuuu que d'informations tu me donne je suis impressionné!!!

=> cela veux donc dire que PB0=not(PB1) et PB3=not(PB4)? Je crois que mon interprétation simpliste n'est pas aussi simple...
Au vu des infos ci dessus, est ce que je peux prendre par exemple PB0 et PB3 pour faire mes 2 port PWM et le reste des port pour l'i2c ou rien?

J'ai essayé de faire une sorte de PWM façon software, voici mon code téléversé dans l'ATtiny85:

struct pwmArtif {
  byte          bPin;     //pin du PWM
  unsigned long ulCycle;  //durée d'un cycle
  unsigned long ulUpLong; //durée de position HIGH
  unsigned long ulPrev;   //tps du début de cycle 
};
pwmArtif pwmA[1];
 
void setup() {
  //paramétrage des PWM
  pwmA[0].bPin = 0;       //pin du PWM
  pwmA[0].ulCycle = 500; //période *** nerf de guerre ***
  //*** 1000=8,0ms  =  125Hz
  //***  500=4,17ms =  239Hz
  //***  100=0,953ms=1,049kHz
  pwmA[0].ulUpLong = map(10, 0, 100, 0, pwmA[0].ulCycle); //1er param est le % up sur le cycle
  pwmA[0].ulPrev = micros();
  
  pinMode(pwmA[0].bPin, OUTPUT);
}

void loop() {
  static boolean boEtat = false;
  
  for (int i=0; i<1; i++) {
    if (pwmA[i].ulPrev > micros()) { //si atteind la limite UL
      pwmA[i].ulPrev = micros();     //reset du cycle
    }
    if (pwmA[i].ulCycle < micros() - pwmA[i].ulPrev  ) { //si le cycle recommence
      pwmA[i].ulPrev = micros();                         //cycle +1
    }
    if (micros() - pwmA[i].ulPrev <= pwmA[i].ulUpLong) { //sinon si on en position HIGH
      if (boEtat == false) {                             //si l'état est LOW
        digitalWrite(pwmA[i].bPin, HIGH);                //mise à l'état HIGH
        boEtat = true;
      }
    } else if (micros() - pwmA[i].ulPrev < pwmA[i].ulCycle) { //sinon si en position LOW
      if (boEtat == true) {                                   //si l'état est HIGH
        digitalWrite(pwmA[i].bPin, LOW);                      //mise à l'état LOW
        boEtat = false;
      }
    }
  }
}

De ce code j'en conclue plusieurs chose:

  • Ça marche à peut prés mais la fonction "micros()" ne renvoie pas la durée en microseconde... il y a un coefficient multiplicateur ou diviseur à y apposer...
  • Plus la période (cycle) est petite plus il est difficile d'avoir une stabilité du cycle, et il arrive même de sauter un cycle si on demande au programme de passer dans des fonctions annexes alors mettre de l'i2c me parait compliquer à gérer...
  • Obtenir du 25Khz (cycle = 40µs) n'est pas réalisable avec ma façon de programmer (je suis nul, mais je me soigne :wink: ) le signal est carrément trop instable est la proportion High/Low du PWM est trop flou...

=> comment, concrètement et simplement, faire varier la fréquence du PWM de deux pattes à 31kHz? je vois pleins d'exemple partout sans rien comprendre parce-que noyé dans leurs propre configuration...

=> Je comprends aussi que l'on peut faire varier la fréquence de l'ATtiny85, comment s'y prendre? la seule façon que je connais c'est de sélectionner "Menu=>Outils=>Clock" et la on peut sélectionner "Internal à 1, 8 ou 16Mhz" ou "External à 8, 16 ou 20 Mhz". Et là je ne comprend pas du tout ce que ces changements ont comme effets... car j’ai beau changer de sélection, rien ne se passe au niveau de mon cycle PWM il à toujours la même fréquence... Il y à certainement quelque chose que j'ai raté, je change de "Clock" et je televerse mon sketch dans l'ATtiny85, y à t'il quelque chose à faire?

=> avec 10 valeurs je serais heureux!!! si il y en à 100 se serais magnifique!!! il faut donc relativiser la précision d'un ventilateur donc 10 ou 20 valeurs se serait top....

=> heuuuuu j'en sais hélas fichtre rien... je vais essayer de me replonger dans la Datasheet mais j'avoue être très peut à même de retrouver mon chemin....

=> Comme vu plus haut, je vais essayer de faire une communication i2c, j'espère y arriver...

=> comme vu plus haut j'ai beau modifier le "clock" apparemment ça ne change rien à mon cycle... Mais merci pour ton image, cela me montre que je n'ai pas la même IHM que toi notamment sur " millis()/micros(): "Enabled" " pourquoi cela? est ce la cause du fait que je n'arrive pas à modifier la période des PWM?

=> ho je sens que tu t'approche d'une solution, mais je n'y comprend pas grand chose :frowning: en terme code cela se traduit comment?

Merci a vous de votre aide. C'est vraiment super!

J'utilise ce portage

On peut faire du SPI hard ou soft. A priori pour le SPI il faut au moins 3 broches (je ne suis pas un spécialiste du SPI). Pour faire du SPI hard, il faut utiliser les broches

  • PB2: SCK
  • PB1: MISO
  • PB0: MOSI

et que PB1 ou PB0 devrait être utilisé pour gérer un PWM, se tourner vers un SPI soft est une solution pour avoir 2 PWM.
Pour une solution Hard, il n'y a qu'un brochage possible, en soft, on utilise les broches que l'on veut.

Si on utilise les deux PB0 et PB1 oui c'est cela!
image

En relisant la doc, pour le PWM, il faut utiliser:
soit PB1 (OC1A)
soit PB1 (OC1A) et PB0 (/OC1A)
-> pour utiliser les deux PWM, il faut les sortir sur PB1 et PB4. Visiblement on ne peut pas utiliser PB0 en PWM et utilise PB1 pour faire autre chose.
image
00 -> pas de PWM
01 -> PWM sur OC1A, /PWM sur /OC1A
10 -> PWM sur OC1A
11 -> /PWM sur OC1A
Il n'y a pas de choix possible, c'est PB1=OC1A et PB4=OC1B


Pas une sorte, c'est du PWM software. Pas de dénigrements. Quelques remarques:

pwmA[1] n'est pas initialisé

pwmA[0].ulUpLong = map(10, 0, 100, 0, pwmA[0].ulCycle); //1er param est le % up sur le cycle c'est simplement pwmA[0].ulUpLong = 100 * pwmA[0].ulCycle /10; //der param est le % up sur le cycle`

Il n'est pas besoin de gérer le dépassement des UL pour le temps, avec micro()-départ, le calcul est valable même quand on arrive au bout

if (micros() - pwmA[i].ulPrev <= pwmA[i].ulUpLong)...

C'est un > pas un <; au début du cycle micros() = pwmA[i].ulPrev la condition devrait être toujours vraie...

micros() s'incrémente de 8µs à chaque fois. La condition
if (pwmA[i].ulCycle < micros() - pwmA[i].ulPrev ) { //si le cycle recommence
passera de façon asynchrone par rapport à micros(). Il es possible par exemple qu'à chaque boucle on passe 1µs plus tôt par rapport à micros(), et une fois sur 8 cela durera 8µs de plus pour rattraper. En plus il va y avoir un effet cumulatif:

    if (pwmA[i].ulCycle < micros() - pwmA[i].ulPrev  ) { //si le cycle recommence
      pwmA[i].ulPrev = micros();                         //cycle +1
    }

pwmA[i].ulPrev ne va pas s'incrémenter de pwmA[i].ulCycle à chaque fois à cause du temps de traitement de instructions. En moyenne il s'incrémentera d'un peu plus. Je conseille de mettre

    if (pwmA[i].ulCycle < micros() - pwmA[i].ulPrev  ) { //si le cycle recommence
      pwmA[i].ulPrev += pwmA[i].ulCycle;                         //cycle +1
    }

Si on va trop vite, tous les tests sont bons, et il n'y a plus de modulation. entre les deux, il y a des problèmes, par exemple, au début c'est bon puis si on augmente la vitesse, le rapport cyclique se rapproche de 50% pour avoir tout fixe.

Un digitalWrite, de mémoire c'est 8µs. Et pour 25kHz (période 40µs) c'est loin d'être négligeable.

Les fréquences en question c'est les fréquences de l'ATiny85, et il faut que ce soit celle de la carte (en principe 8MHz). Changer la fréquence peut changer par exemple l'incrémentation de micros(). Si on met un quartz à 8MHz et que l'on déclare cette fréquence, micros() s'incrémente de 8µs à chaque 8µs.
Si on met un quartz à 16MHz et que l'on déclare cette fréquence, micros() s'incrémente de 4µs à chaque 4µs.
Mais si on met un quartz à 8MHz et que l'on déclare que le quartz fair 16MHz, micros() s'incrémente de 8µs à chaque 4µs.
Mettre une fausse valeur "dérègle" l'horloge interne (delay, millis, micros). C'est peut être pour cela que tu constate une erreur sur micros()

Il faut demander au timer de délivrer du PWM à cette fréquence. Et il n'y a pas le choix, il faut passer par les registres. La lecture de la datasheet de l'Atiny85 est incontournable.
Partie: 12 => 8-bit Timer/Counter1
Sous partie: 12.3 => Register Description

Je programme le premier registre dans le post qui suit.

Bien entendu, il faut faire le test, je n'ai pas le µP sous la main. Il est donc possible que je fasse une erreur (normalement j'en fais toujours une quelquepart)

image
Bit 7 – CTC1 : Clear Timer/Counter on Compare Match => a priori, ce bit ne sert pour le mode CTC qui ne nous intéresse pas =>CTC1=0
Bit 6 – PWM1A: Pulse Width Modulator A Enable =>PWM1A=1 pour avoir le PWM

image
On veut utiliser PB1 (OC1A) => COM1A1/0 = 10
image

TCCR1 = 0b01100101; // PWM sur PB1, 25 kHz


image

PWM1B=1 pour avoir le PWM

COM1B1/0 = 01 (pareil que COM1A1/0)

FOC1B et FOC1A ne servent à rien en PWM (foc you?)

PSR1 est une raz

GTCCR = 0b01100000; // PWM sur PB4


TCNT1 – Timer/Counter1
C'est le compteur, il tourne tout seul, on n'a pas besoin d'y toucher.


OCR1A –Timer/Counter1 Output Compare RegisterA
c'est là qu'on y met le rapport cyclique pour PB1=OC1A
0 pour 0%
160 pour 100%
car le compteur ne compte que jusqu'à 160
OCR1A = 16; // pour 10%


OCR1B –Timer/Counter1 Output Compare RegisterB
c'est là qu'on y met le rapport cyclique pour PB4=OC1B
0 pour 0%
160 pour 100%
car le compteur ne compte que jusqu'à 160
OCR1B = 32; // pour 20%


OCR1C – Timer/Counter1 Output Compare RegisterC
pour avoir les 25kHz (64MHz / 16 / 160)
OCR1C = 160; // pour 25kHz


TIMSK – Timer/Counter Interrupt Mask Register
=> pas d'interruptions, il est à 0 lors de l'initialisation, on n'a pas besoin d'y toucher.


Il faut aussi que l fréquence de 64MHz soir effective:
image
Bit 7 – LSM: Low Speed Mode => bit à 0, sinon c'est du 32MHz
Bit 2 – PCKE: PCK Enable => bit à 1 pour choisir le 64MHz au lieu de l'horloge à 8MHz
Bit 1 – PLLE: PLL Enable => doit être à 1 pour créer le 64MHz
Bit 0 – PLOCK: PLL Lock Detector => lecture seule
PLLCSR = 0b00000110; // pour fabriquer le 64MHz

le programme pour faire du PWM sur PB1 à 10% à 25kHz et du 20% sur PB4 devrai être:

void setup() {
  TCCR1 = 0b01100101; // PWM sur PB1, 25 kHz
  GTCCR = 0b01100000; // PWM sur PB4
  OCR1A = 16; // pour 10% sur PB1
  OCR1B = 32; // pour 20% sur PB4
  OCR1C = 160; // pour 25kHz
  PLLCSR = 0b00000110; // pour fabriquer le 64MHz
}

void loop() {
}

Il ne reste plus qu'à débugger et/ou attendre des autres qu'ils disent ou est mon(mes) erreur(s).

1 Like

@vileroi Hoooooo toi je t'aime!!!!! tu est mon Dieu!!!
j'ai envie de dire plein de gros mots tellement c'est bon de voir enfin un bon résultat! et en plus tu me donne des explications que j'arrive enfin à comprendre!!!!
au code que tu m'as donné j'ai juste rajouter ces 2 lignes:

  pinMode(1, OUTPUT);
  pinMode(4, OUTPUT);

sinon ça ne donnait rien....
voila le résultat:
IMAG001
ho que c'est bon de voir cela!!!!

bon maintenant je m’attelle la communication i2c...

à très vite!

Je suis bien content que cela fonctionne et d'avoir un compte rendu.
J'avais dit qu'il y avait au moins une erreur...
Et faut remercier @Artouste aussi qui a pointé l'horloge à 64MHz

Par contre je suis parfaitement incompétent pour le I2C. Il faut penser soit à faire un autre post car le sujet sera différent.

Si il en manque n'hésites pas.