pwm 200Hz

Bonjour,

J'ai compris de la doc que je ne peux choisir que des fréquences de pwm imposées par la fréquence de base et des multiplicateurs.
Je voudrais réaliser un pwm de fréquence fixe = 200Hz dont la largeur d'impulsion est fonction d'une tension variable ( 0/5v) avec une carte UNO.
Je n'ai pas trouvé, à ce jour, d'articles traitant de ce sujet, quelle solution ?

Merci

Au mieux avec les diviseurs tu obtiendras 245.10 Hz.

Pour une fréquence précise, il faut faire du PWM à la main, avec du code (digitalWrite(HIGH), delay(), digitalWrite(LOW), delay(), etc.).

hello
testes ce code

#define toggle_pulse digitalWrite (PULS, !digitalRead(PULS))

#define freq_ext            2                             
#define reserve             3//D3 reservé, ne pas utiliser. en sortie pour SAELAE
#define PULS                8
#define POTAR              A0
bool  isr         =     false;
int   compteur    =         0;
int   duree_puls  =        50; 
int   complement  =        50;   
int   compt       =duree_puls;

void setup() 
{
  pinMode(PULS,      OUTPUT); digitalWrite(PULS, HIGH); 
  pinMode(reserve,   OUTPUT);
  pinMode(freq_ext,  INPUT_PULLUP);
  Serial.begin(1000000);
  cli();                  
  TCCR2A = 0b00010000; // 
  TCCR2B = 0b00000010; // 
  TIMSK2 = 0b00000001; // 
  sei();
  isr =true;
}

void loop() 
{
  duree_puls=map (analogRead(POTAR),0,1023,0,100);
  complement=100-duree_puls;
  //Serial.print(duree_puls);Serial.print("  ");Serial.println(complement);
}
ISR (TIMER2_OVF_vect)
{
  if (isr)
  { TCNT2=164;
    if (compteur++ >= compt )
    {
      toggle_pulse;
      compteur = 0;
      if (compt == duree_puls)
      {
        compt = complement;
      }
      else
      {
        compt = duree_puls;
      }
    }
  }
}

Déjà présenter le matériel dont on dispose me parait être tellement une bonne chose que c'est même obligatoire => voir (et lire !) les messages épinglés dont : Règles du forum francophone.

Si, comme notre boule de cristal semble nous l'indiquer, tu as une carte à base de micro Atmel avr il y a moyen de faire plus de chose avec les timers que ce qu'en font les fonctions arduino .
Un exemple parmi d'autres : un timer peut servir d'horloge à un autre timer et donc augmenter les possibilités.
Voir ici : Site de Mike Gammon

Mais il va falloir mettre les mains dans le cambouis.

Bonsoir,
Merci à tous

-68jts : le matériel est pour moi décrit = carte UNO, je peux ajouter un oscillo + le moniteur série si ça peux intéresser.
Je vais regarder le site de MG. Ce n'est pas le cambouis qui pourrait me rebuter mais la complexité de la chose en rapport de mes faibles connaissances.

  • dfgh : je vais commencer par tester le code en espérant saisir le principe du code ( peu de commentaires ! ) et l'utilisation de fonctions d'interruption ( ISR)

  • hbachetti : aurais-tu quelques exemples utilisant les instructions mentionnées (digitalWrite(HIGH), delay(), digitalWrite(LOW), delay(), etc.) que je ne retrouve pas dans le code de dfgh ? Principe différent ?

nota : y-a-t-il une raison pour que cet "outil" (pwm de fréquence fixe et choisie) n'existe pas en magasin et que surtout que le concept de ce code ne soit pas documenté?

(post deleted by author)

Tu n'as pas précisé le rapport cyclique, c'est à dire le rapport entre les deux durées HIGH et LOW. On suppose que c'est 50%.

Donc, la période complète du signal PWM est de 5ms soit 2.5 ms HIGH et 2.5ms LOW.

La précision de delay est de l'ordre de 4 ms si j'ai bonne mémoire donc une solution qui utilise un delay ne sera pas précis. Il faut utiliser delayMicroseconds

void loop() {
 digitalWrite(pin, HIGH);
 delayMicroseconds (2500);
 digitalWrite(pin, LOW);
 delayMicroseconds (2500);
}

Pour être plus précis il faudrait mesurer le temps des instructions digitalWrite et soustraire ce temps des delayMicroseconds

Pour mesurer ce temps tu fais une boucle sur un million de digitalWrite et tu mesures le temps avec l'instruction millis, dans un autre sketch bien sûr.

Principe différent ?

Forcément.
Soit tu utilises des timers comme conseillé par dfgh, si ton code est appelé à faire autre chose pendant la génération du PWM, soit tu utilises delay().
Comme tu ne précises pas ce que doit faire ton programme en dehors de générer un PWM, je réponds au plus simple.

  • hbachetti : aurais-tu quelques exemples utilisant les instructions mentionnées (digitalWrite(HIGH), delay(), digitalWrite(LOW), delay(), etc.)

Voir ce qu'à écrit lesept et adapter le rapport cyclique.

hello
j'ai revu le code pour etre sur d'avoir le pulse sur la bonne valeur du potar.
il y avait possiblité d'inversion

//#define toggle_pulse digitalWrite (PULS, !digitalRead(PULS))

#define freq_ext            2                             
#define reserve             3//D3 reservé, ne pas utiliser. en sortie pour SAELAE
#define PULS                8
#define POTAR              A0
bool  isr         =     false;
int   compteur    =         0;
int   duree_puls  =        50; 
int   complement  =        50;   
int   compt       =duree_puls;

void setup() 
{
  pinMode(PULS,      OUTPUT); digitalWrite(PULS, HIGH); 
  pinMode(reserve,   OUTPUT);
  pinMode(freq_ext,  INPUT_PULLUP);
  Serial.begin(1000000);
  cli();                  
  TCCR2A = 0b00010000; // 
  TCCR2B = 0b00000010; // 
  TIMSK2 = 0b00000001; // 
  sei();
  isr =true;
}

void loop() 
{
  duree_puls=map (analogRead(POTAR),0,1023,0,100);
  complement=100-duree_puls;
  //Serial.print(duree_puls);Serial.print("  ");Serial.println(complement);
}
ISR (TIMER2_OVF_vect)
{
  if (isr)
  { TCNT2=164;
    if (compteur++ >= compt )
    {
      //toggle_pulse;
      compteur = 0;
      if (compt == duree_puls)
      {
        compt = complement;digitalWrite (PULS,LOW);
      }
      else
      {
        compt = duree_puls;digitalWrite (PULS,HIGH);
      }
    }
  }
}

-68jts : le matériel est pour moi décrit = carte UNO,

Exact, donc "pan sur le bec" comme ils disent au canard.
Néanmoins je persiste à conseiller l'intégralité du site de Mike Grammon.

Bonsoir
Pour préciser je souhaite avoir une fréquence de base de 200Hz aussi précise que possible et faire varier ( potar) la durée "High" de ~ 0 à 100% soit par exemple de 100 microsec à 5 ms

Autre précision si nécessaire, peu de taches additionnelles à effectuer sinon des mesures via un LCD ( mesure de la tension variable généré par le potar et de la durée du pulse "hight").?
Est-ce une contrainte si besoin de taches supplémentaires ?

Je voudrais réaliser un pwm de fréquence fixe = 200Hz dont la largeur d'impulsion est fonction d'une tension variable ( 0/5v) avec une carte UNO.
Je n'ai pas trouvé, à ce jour, d'articles traitant de ce sujet, quelle solution ?

Je suis effaré par l'utilisation d'interruptions pour faire du PWM. Les timers de l'Uno permettent de faire du PWM hardware. Avec le timer 1 qui est un 16 bits, en utilisant le mode "Phase and Frequency Correct PWM Mode", on a une précision de 62.5ns sur la période (via ICR1) et 65535 pas sur le rapport cyclique (via OCR1A). Il y a juste 4 ou 5 registres à déterminer dans le setup, et pas de fonctions d'interruptions.

Mettre la main dans le camboui, c'est prendre la doc de l'ATmega328, et voir le mode PWM. Je reconnais que ce n'est pas extrêmement simple.

As tu testé le code que je t'ai passé en#8

Dans la loop, j'ai commenté la ligne d'affichage sur le Serial.
De la à afficher sur lcd, il n'y a qu'un pas.

Bonsoir,

68tjs : j'ai parcouru le tuto de MG, certainement intéressant mais réservé aux spécialistes. Comme le dit Vileroi "Mettre la main dans le camboui, c'est prendre la doc de l'ATmega328 etc. » merci quand même….

Dfgh : j’ai testé ton code et à des détails prés, il fonctionne bien : j’obtiens bien une fréquence de base de 200Hz et je peux moduler de # 5 à 95 % en terme de duty cycle
La précision à l’oscillo numérique sur la fréquence de base est d’environ 1,5Hz/200 soit # 0,8 % ce qui doit être relié à la fréquence du timer (?)
Par contre j’ai une sorte de jitter sur le signal et je perd de temps en temps un pulse, la valeur moyenne de la fréquence de base restant dans la fourchette indiquée ( problème HW ? ).

A vrai dire je n’ai pas compris ni comment ça marche ni la logique du code (obtention de la fréquence 200Hz etc.) et j’ai bêtement recopié ton code ( mon niveau actuel est limité ! ). Mais si tu pouvais m’en dire plus ( principe du code et commentaires dans le code) ce serait parfait.

68tjs : j'ai parcouru le tuto de MG, certainement intéressant mais réservé aux spécialistes.

Lire une datasheet ça fait peut-être peur au début mais cela ne casse pas 3 pattes à un canard et c'est souvent la seule solution pour dépasser les fonctions arduino.
Une datasheet Atmel-avr 8 bits c'est clair et logique, ce serait une datasheet ARM 32 bits ce serait moins simple.
90% du contenu de la datasheet d'un micro avr-8bits c'est des recettes de cuisine : le micro peut faire ça, pour faire ça il faut mettre telles valeurs dans tels registres. Un registre ce n'est rien d'autre qu'une adresse mémoire particulière. Tout a été prévu par Atmel pour qu'on puisse y accéder sans prise de tête.

Nick Gammon connait tellement bien ces micros avr-8bits qu'il sait faire travailler ensemble des choses apparemment séparées.

Par contre j'ai une sorte de jitter sur le signal et je perd de temps en temps un pulse, la valeur moyenne de la fréquence de base restant dans la fourchette indiquée ( problème HW ? ).

En passant par les interruptions, suivant quand se déclenche l'interruption, cela va retarder ou non les changements. Pour peu que la remise à l'heure se fasse, il va y avoir un délai supplémentaire.

En utilisant le circuit PWM présent dans l'Arduino, on a un signal qui est hard, et donc:

  • n'est pas influencé par les interruptions
  • ne prends pas te temps machine autre que pour le lancement.

C'est analogWrite(), que l'on peut utiliser tel quel si on se contente des valeurs imposées, sinon il suffit de réécrire cette fonction. Pour répondre au problème "Je voudrais réaliser un pwm de fréquence fixe = 200Hz dont la largeur d'impulsion est ajustable", on peut utiliser plusieurs modes, par exemple le mode 14 de la datasheet Atmel "Fast PWM Mode", c'est le premier qui répond au problème.

  1. Il faut utiliser le mode 1410=11102
  2. Le timer 0 étant utilisé par le système, je choisis le timer 1 (on peut aussi prendre le timer 2 qui est un 8 bits, avec une précision moindre sur le rapport cyclique)
  3. Avec le timer 1 on ne peut utiliser que les sorties OC1A ou OC1B. je choisis la première OC1A qui est aussi appelée sortie 9
  4. Pour l'horloge, je choisis 0,5µs (division de l'horloge système par 8) moins on divise, plus on a un réglage fin, mais point trop n'en faut si on doit compter sur 1 bits? Pour une période de 5mS, on doit compter 10000. On ne peut pas compter 8 fois plus, cela dépasse. En fait il faut compter un de moins que ce que l'on veut soit 9999.

Voici alors un programme avec un PWM de 200Hz, rapport cyclique de 33%:

void setup()
{
  pinMode(9, OUTPUT); // OC1A, Uno, sortie PWM du Timer 1
  TCCR1A = B10000010; // Sortie OC1A, OC1B non utilisé, Mode 14=fast PWM 
  TCCR1B = B00011010; // Pas d'interruptions, Mode 14, Horloge 0,5µs
  ICR1 = 9999; // Période 5ms ((9999+1) x 0,5µs)
  OCR1A = 3333; // Etat haut 
}
void loop()
{
}

Pour ajuster la période (elle est à 200Hz si le micro tourne à 16000000,0000Hz). Sinon, il suffit de changer la valeur de ICR1

Pour changer le rapport cyclique, il faut changer la valeur de OCR1A, 0 pour 0%, 10000 pour 100%

Petit bémol avec ce code aussi simple: si on diminue la valeur de OCR1A, il est possible que la comparaison ne se fasse pas si le compteur a une valeur instantanée supérieure à la nouvelle valeur, et on aura un défaut sur une période. Pour éviter cela, si c'est utile, il faut changer la valeur de OCR1A quand le compteur TCNT1 est inférieur à OCR1A. Du fait du temps de traitement, cela risque d'être impossible pour des rapports cycliques inférieurs à 1%.

Le code ci dessus a été testé, maintenant si la valeur vient de analogRead(POTAR), on devrait pouvoir écrire (non testé):

void loop()
{
  OCR1A = analogRead(POTAR) * 10; // transformation simple 0-1024 -> 0-10240
}

Bonjour,

Alors vileroi je te dis chapeau, je m’y suis mis à plusieurs reprises pour vérifier le signal PWM à l’oscillo

  • fréquence stable 200 Hz , la lecture ne me donne pas accès au décimales donc au pire une erreur < 0,005
  • pas de jitter sur les fronts et pas d’interruptions dans le signal

En ~ 6 lignes de code, tu résous mon problème et de façon plus générale tu complètes la panoplie Arduino avec un pwm ajustable digne de ce nom.

Tes explications ligne à ligne sont claires. Malheureusement en ce qui me concerne je ne pourrais les comprendre que si je me plonge dans l’univers des timers et registres associés. Il me semble que l’on se rapproche ( dangereusement ) de la machine ce qui est pour moi, à mon age, une aventure risquée...

nota : je ne cherche pas à aller en deçà de 1% en rapport cyclique pas de changement du code si j'ai bien compris, mais au cas ou, il y aurait une solution ?

Par contre quelques conseils supplémentaires me seraient très utiles : je voudrais visualiser sur mon pavé LCD les valeurs de la fréquence mesurée ainsi que le pourcentage, à un moment donné, du duty cycle(%).

J’ai compris que les valeurs fixe ou variable données aux registres ICR1 ( 200 Hz) et OCR1A (état haut) permettent de fournir le signal PWM sur la sortie numérique 9. Faut-il créer des variables à partir des valeurs de ICR1 et OCR1A et par un calcul simple d’obtenir ce duty cycle ? J’ai essayé sans trop de succès !

Merci encore quoiqu’il arrive

bonjour,

Samanu:
(@Vileroi)
Tes explications ligne à ligne sont claires. Malheureusement en ce qui me concerne je ne pourrais les comprendre que si je me plonge dans l’univers des timers et registres associés.

c'est la seule solution si tu veux sortir de la zone de confort créée par Arduino, et personnaliser le fonctionnement de tes montages.


je voudrais visualiser sur mon pavé LCD les valeurs de la fréquence mesurée ainsi que le pourcentage, à un moment donné, du duty cycle(%)

tu connais déjà la fréquence, à moins de jouer sur la valeur d'ICR1 : pour la calculer, avec les réglages de prescaler effectués par Vileroi, il te suffit de faire :
(ICR1+1)*500ns
avec 500ns = durée d'1 cycle d'horloge du timer avec ces réglages :
prescaler = 1/8 ==> période timer = 1/(16MHz/8).

pour le rapport cyclique :
(OCR1A+1)/(ICR1+1).

(ce n'est pas du code !)

si tu te plonges dans la doc du µC, tu verras que ce n'est pas si difficile, ça peut même devenir ludique.

Merci à 5_cylindres de prendre le relais, je vais prendre un petit anticonfinement.

Rien à redire.

Faut-il créer des variables à partir des valeurs de ICR1 et OCR1A

On peut considérer que ICR et OCR1A sont des variables (on peut les lire et les écrire).

Sinon on peut dire que c'est presque une bibliothèque possédant une fonction
MonPWM(periode_en_demi_nanoseconde, etat_haut_en_demi_nanoseconde)
le reste du code peut être utilisé tel quel.
Il n'y a que ces deux paramètres intéressants. Et encore si tu désires 200Hz, il n'y en a plus qu'un.

tu verras que ce n'est pas si difficile

C'est plus facile quand on a un exemple, il y a 15 modes différents, et ce n'est pas facile de trouver le bon. Mais quand on a le mode, cela divise la lecture au seul paragraphe utilisé.

En tout cas, pour moi, c'est plus facile d'écrire ce code (avec ce mode que je n'avais jamais utilisé) que d'utiliser une bibliothèque toute faite, comme FatSD ou AccelStepper!

Tes explications ligne à ligne sont claires. Malheureusement en ce qui me concerne je ne pourrais les comprendre que si je me plonge dans l'univers des timers et registres associés. Il me semble que l'on se rapproche ( dangereusement ) de la machine ce qui est pour moi, à mon age, une aventure risquée...

Je ne dis pas non plus que j'ai tout compris, ni du premier coup. J'ai aussi fait plusieurs mauvais essais.