Plus d'info sur l'utilisation des Timer par l'environnement Arduino

Je rejoins ce que dit 68jts,

les pin OCxn de Atmel correspondent aux pins PWM de l'arduino, on peut donc penser que pour faire de la PWM ils utilisent les fonctions cablés prévues dans le microcontrôleur: c'est plus simple (et plus logique)

Mais les 6 pins PWM arduino correspondent aux sorties PWM pour les TROIS timers (OC0A,OC0B,OC1A,OC1B,OC2A,OC2B). donc en plus de gérer la PWM, ces timers servent pour les fonctions millis() et dérivées?

Comment s'assurer que ces timers sont dispo pour créer des ISR d'overflow ou autres?

(ces remarques sont pour les arduino basés sur un ATmega328)

MrAlexis44600:
Comment s'assurer que ces timers sont dispo pour créer des ISR d'overflow ou autres?

il suffit de savoir sur quelles pins on fait un analogWrite(), si on utilise la pin OC1B par exemple, alors le timer1 n'est plus dispo, à moins de générer la PWM soit-même et il reste possible alors d'utiliser OCR1A pour des int internes, mais faut super bien réfléchir à la chose!

Je trouve qu'arduino est trop éloigné de l'atmega, car utiliser le datasheet atmel et la carte arduino en même temps demande une réflexion double (penser arduino et atmega en même temps, chaque fonction de l'un est différente chez l'autre), c'est une gymnastique pas facile. Mais quand on y arrive, alors on obtient une certaine fierté perso (du vécu)...

les pin OCxn de Atmel correspondent aux pins PWM

C'est ce que j'avais compris en lisant la datasheet, merci de ta confirmation.

Pour info hier soir j'ai trouvé un site donnant des explications très claires :
http://aquaticus.info/pwm

68tjs:
J'en profite pour demander comment peut-on identifier avec certitude dans la datasheet du micro-controleur si une sortie de port peut ou non faire de la pwm.

Il suffit de regarder le "pinout" du composant et chercher l'emplacement des broches annoté OCxn.
Puis de faire un tour dans les différentes rubriques timer/pwm et regarder le schéma de principe de celles ci.

MrAlexis44600:
les pin OCxn de Atmel correspondent aux pins PWM de l'arduino, on peut donc penser que pour faire de la PWM ils utilisent les fonctions cablés prévues dans le microcontrôleur: c'est plus simple (et plus logique)

La PWM "arduino" est généré en utilisant les générateurs de signaux PWM matériel, seul la librairie Servo utilise les timers "en timer" seul pour faire de la PWM logiciel.

MrAlexis44600:
Mais les 6 pins PWM arduino correspondent aux sorties PWM pour les TROIS timers (OC0A,OC0B,OC1A,OC1B,OC2A,OC2B). donc en plus de gérer la PWM, ces timers servent pour les fonctions millis() et dérivées?

Oui, cependant il ya une GROSSE astuce pour cela :wink:

Un signal PWM à toujours une fréquence fixe, en général dans les alentours de 500Hz ou 1KHz pour bien faire.
Le timer 0 est configuré pour générer les "tick" (interruption "on compare match") nécéssaire au bon fonctionnement de millis(), ...

En parallèle le générateur de signal PWM est indépendant, il est juste basé sur la fréquence/registre OCRxn du timer.
La team arduino n'as donc pas chercher bien loin, ils génèrent tout simplement le signal PWM sur OC0A et OC0B sans se soucié dans la fréquence du signal.
Ce qui laisse la partie interruption fonctionner normalement, et de faire de la pwm "pas trop mal" en même temps.

MrAlexis44600:
Comment s'assurer que ces timers sont dispo pour créer des ISR d'overflow ou autres?

Il n'existe pas de fonction hardware ou software pour cela.
Il faudrait lire la doc du core arduino en profondeur ... si elle existait ...

Je n'ai pas compris les dernières explications: le timer0 est utilisé pour appeler des ISR pour la fonction millis... et la PWM? elle est fabriquée dans l'ISR du coup? car on ne peut pas utiliser le fonctionnement interruption et pwm en même temps, il y une configuration bien précise des registres pour chaque utilisation...

Donc si je n'ai pas tout faux jusque là (c'est à dire si la PWM est fabriqué dans la routine d'interruption) pourquoi est elle associée aux pins correspondants OC0A et 0COB alors qu'ils auraient pu les laisser parfaitement libre...

(D'ailleurs je trouve qu'il est dommage de pas pouvoir générer plus de PWM / de signaux de servos, si on utilise les 3 timers on doit bien pouvoir dépasser les 12 signaux... et atteindre les 19:le nombre d'I/O!)

Bon en relisant ta réponse je viens de voir que je n'avais pas compris :sweat_smile: Donc si tu peux réexpliquer l'astuce, ça m'intéresse XD

Un signal PWM à toujours une fréquence fixe, en général dans les alentours de 500Hz ou 1KHz pour bien faire.

Attention de ne pas généraliser, ce que tu dis n'est valable que si on reste dans l' "univers de programmation arduino".
Comme il est écrit sur le site http://aquaticus.info/pwm-frequency le micro-controleur permet différentes fréquences en fonction du choix du préscaller (dans l'exemple du site la fréquence peut varier de 30 kHz à 30Hz).

En résumé il ressort de ces échanges très constructifs que mixer les univers arduino/avr-libc est super casse cou.

  • il faut bien identifier les ressources matérielles utilisées par le micro lui même, ce n'est pas simple
    --> outil : lire et relire la datasheet
  • il faut bien identifier les timers utilisés par les bibliothèques arduino, c'est malheureusement encore moins simple
    --> outil : le site arduino mais il est atrocement déficient.

Si on est obligé d'utiliser un timer déjà utilisé par les bibliothèques arduino il faut marcher sur des oeufs avant de modifier sa configuration.

Je vois là une idée d'un beau travail collectif pour créer un document qui préciserait l'état et la configuration de tout les timers pour l'ATMega328p et l'ATMega2560.

Ok après relecture de la datasheet et un peu de réflexion, je pense avoir compris commen les dev ont fait pour gérer les millis() en même temps que la pwm...

mode FAST pour la PWM, ce qui donne 2 sorties; et peut importe le mode de PWM choisie, ils ont configuré une routine d'interruption qui se déclenche au TOP (=MAX) du timer pour incrémenter des variables qui stocke le temps écoulé depuis la mise sous tension du bouzin. Et pour avoir une résolution à 4 µs près, ils regardent en plus où en est rendu le timer (entre 0 et 255)

Et comme le dis 68tjs, si on touche à la fréquence de découpage, on touche (au moins?) aux fonctions millis, micros et delay; et je trouve dommage que le site Arduino ne donne pas plus de détails, je n'ai absolument pas le niveau pour relire le code source de analogWrite() et millis().

Pour la coupe de robotique l'année prochaine je compte mélanger code Arduino et AVR, j'essaierai de penser à faire un retour d'expérience.

68tjs:
Attention de ne pas généraliser, ce que tu dis n'est valable que si on reste dans l' "univers de programmation arduino".

Oui je reste dans le cadre "arduino", pour de la "vrai" PWM (fiable et bien conçu) on préférera des fréquences de plusieurs KHz pour quelle ne soit pas audible.
(Du reste en bricolant un peu dans le sens inverse il est possible de transformer n'importe quel moteur en instrument de musique ;))

68tjs:
En résumé il ressort de ces échanges très constructifs que mixer les univers arduino/avr-libc est super casse cou.

En résumé rien ne vaut eclipse, la toolchain avr-gcc et la doc de la avr-libc :wink:
Arduino c'est bien, mais dés que l'on veut faire quelques chose de plus poussé avr-c c'est bien mieux (et plus simple parfois).

MrAlexis44600:
Ok après relecture de la datasheet et un peu de réflexion, je pense avoir compris comment les dev ont fait pour gérer les millis() en même temps que la pwm...

Regarde dans wiring.c -> tu auras le fonctionnement de millis() et micros()
Regarde dans wiring_analog.c -> tu auras le fonctionnement de analogWrite()
Le principe est simple, le générateur de PWM est toujours en marche avec une interruption de type timer_overflow (disponible en mode FastPWM).

L'interruption incrémente le compteur de millis() et un compteur de "fraction de temps", millis() ne fait que lire son compteur, micros() lui se base sur le compteur de fraction de temps et l'état courant (à l'appel de micros()) du compteur du timer pour déduire un nombre de cycles, précis à 4µs prés.

AnalogWrite() ne fait qu'une seule chose, elle connecte la sortie du générateur de PWM à la broche OCxn en plaçant le "1" dans la case du registre qui va bien.
Et elle modifie le registre du générateur de PWM OCR0x en conséquence.

L'interruption de type 'overflow est donc toujours au même moment (à l'overflow du timer) quelque soit la valeur du générateur de PWM.
Une pierre, deux coups.

MrAlexis44600:
Et comme le dis 68tjs, si on touche à la fréquence de découpage, on touche (au moins?) aux fonctions millis, micros et delay; et je trouve dommage que le site Arduino ne donne pas plus de détails, je n'ai absolument pas le niveau pour relire le code source de analogWrite() et millis().

Le site arduino a un gros défaut, la documentation n'est que superficielle.
Pour un débutant c'est parfait, pour un utilisateurs expérimenté c'est insupportable.

En plus le code source en lui même contient des choses incohérente, des commentaires à faire pâlir un dév (du genre "this should works"), et mériterai une refonte complète sur certains points ...

MrAlexis44600:
Pour la coupe de robotique l'année prochaine je compte mélanger code Arduino et AVR, j'essaierai de penser à faire un retour d'expérience.

Mauvaise idée :wink:
Si le code doit être publié, soit tu écrit tout en arduino, soit tout en avr-c/c++ ne t'amuse pas à mélangé les genres ça ferait pas bonne figure.

skywodd:
Mauvaise idée :wink:
Si le code doit être publié, soit tu écrit tout en arduino, soit tout en avr-c/c++ ne t'amuse pas à mélangé les genres ça ferait pas bonne figure.

Mouais, mais certaines fonctions arduino comme le Serial sont bien pratiques pour débugger... et de toutes manières quand on achète les Arduino MEGA le Bootloader est installé, je voulais savoir si de base les timers étaient pré configurés.

Et puis si j'annonce du 100% avr-c/c++ je vais faire fuir tous les membres du club :stuck_out_tongue_closed_eyes:

Bon à titre d'exemple, voici ce que je retiens de la lecture du datasheet à propos des registres concernant les interruptions :

INT0=digital PIN 2 (la PIN 2 de l'Arduino)
INT1=digital PIN 3 (la PIN 3 de l'Arduino)
port D=PIN de 0 à 7 de l'Arduino
port B=PIN de 8 à 13 de l'Arduino (14 n'étant pas documenté côté Arduino)
port C=PIN A0 à A5 (l'équivalent du A6 étant le RESET, la A7 n'est pas documenté)

SREG [I;T;...] pour activer globalement les interruptions
Le I-bit est : Global Interrupt Enable (modifié notamant par SEI, CLI)

EICRA [...ISC11;ISC10;ISC01;ISC00] pour parametrer les type d'interruption INT1 & INT0
ISC11 & ISC 10 : pour INT1
ISC01 & ISC 00 : pour INT0
Se paramètre comme suit :
00 Low level
01 Change (falling & Rising)
10 Falling
11 Rising
Exemple : EICRE = [0;0;0;0;0;0;1;0] veut dit que INT1 est déclenché par Low level & INT0 est déclenché par Falling edge (front déscendant)

EIMSK [...;-;INT1;INT0] Pour activer les interruption sur INT1 & INT0
Enable or Disable INT1 INT0 interruption

EIFR [...;-;INTF1;INTF0] Flag quand les interruption INT sont déclenchées
Flags when interrupt are triggered (reset when interruption is executing)

PCICR [...;-;PCIE2;PCIE1;PCIE0] pour activer le déclenchement d'interruptions sur les ports D, C, B

PCIMSK2 [PCINT23;...;PCINT16] pour selectionner les PIN du PORT qui vont déclencher l'interruption PCIE2 (portD)
PCIMSK1 [-;PCINT14;...;PCINT8] pour selectionner les PIN du PORT qui vont déclencher l'interruption PCIE1 (portC)
PCIMSK0 [PCINT7;...;PINT0] pour selectionner les PIN du PORT qui vont déclencher l'interruption PCIE0 (portB)

PCIFR [...;-;PCIF2;PCIF1;PCIF0] Flag quand les interruption PCI sont déclenchées
Flags when interrupt are triggered (reset when interruption is executing)

...

Voila ce n'est qu'un exemple, je cherche la forme appropriée qui peremettrait une utilisation "simple"... mais c'est à peu près ce qu'il me faut pour comprendre et pouvoir utiliser les timers.

Pour les interruptions PCINTx je m'étais un document que j'ai rendu disponible là :
http://arduino.cc/forum/index.php/topic,100906.0.html

Ton doc est très chouette, j'aurai bien aimé tomber dessus il y a quelques temps... :sweat_smile: ça m'aurait évité de me fourvoyer à ce sujet.

Le même doc qui traiterait des counter/timer me serait très utile.

Sev

Il fallait regarder dans le sous forum tutoriaux !

Il y a aussi "Le guide Arduino" épinglé en tête du forum principal qui est une mine de renseignements.

Quand on dit qu'il faut se renseigner avant .........

Je ne connaissais malheureusement pas ce forum lorsque j'ai travaillé sur la partie interruption.

En revanche, j'ai déjà parcouru la partie "Le guide Arduino" et je n'y ai pas vraiment trouvé ce que je cherchais, qui n'est autre que la couche non-documentée de l'Arduino, c'est à dire toutes les librairies "invisibles" de l'environnement Arduino.

Il serait bon de documenter cette partie, au moins pour que les débutants qui se mettent au C/C++ pour l'Arduino puissent savoir quel commande est du C générique et quelle commande appartient à la couche Arduino.

Sev

Vaste programme !

Suite de fork et autres évolutions
Processing -> Wiring --> Arduino

Je ne sais pas si une mère y retrouverait ses petits.

J'aimerais vraiment que le soft Arduino permette 2 modes de compilation, avec et sans l'environnement Arduino, ça allègerait forcément les devs de certains projets.

Bon j'avance sur mon "pense-bête" sur les timers, la première partie rappelle les spécificités des timers, la seconde n'est qu'un dictionnaire de registre, dans lequel il manque pour l'instant les définitions :

Le timer0 et timer2 sont similaires en tout point, ce sont tous les 2 des timer 8-bits, sauf que le timer2 peut être "clocké" sur une source externe asynchrone.

Le timer1 est un peu différent, puisque c'est un timer 16-bits et qu'il embarque un compteur qui peut horodater les évenement capturé sur un PIN (timestamp), avec un atténuateur de bruit activable.

RAPPEL SUR LES REGISTRES

Le registre commun
GTCCR [TSM ;-…-;PSRASY;PSRSYNC] General Timer/Counter Ctrl Reg

Les registres du Timer 0
TCCR0A [COM0A1;COM0A1;COM0B1;COM0B0;-;-;WGM01;WGM00] …
TCCR0B [FOC0A;FOC0B;-;-;WGM02;CS02;CS01;CS00] …
TCNT0 = Counter, OCR0A = ComparMatch0A, OCR0B = ComparMatch0B
TIMSK0 [...-;OCIE0B;OCIE0A;TOIE0] …
TIFR0 [...-;OCF0B;OCF0A;TOV0] …

Les registres du Timer 1
TCCR1A [COM1A1;COM1A1;COM1B1;COM1B0;-;-;WGM11;WGM10] …
TCCR1B [ICNC1;ICES1;-;WGM13;WGM12;CS12;CS11;CS10] …
TCCR1C [FOC1A;FOC1B;-...] …
TCNT1H = CounterH, TCNT1L = CounterL
OCR1AH & OCR1AL = ComparMatch1A, OCR1BH & OCR1BL = ComparMatch1B
ICR1H & ICR1L = Input Capture Register1
TIMSK1 [-;-;ICIE1;-;-;OCIE1B;OCIE1A;TOIE1] …
TIFR0 [-;-;ICF1;-;-;OCF1B;OCF1A;TOV1] …

Les registres du Timer 2
TCCR2A [COM2A1;COM2A1;COM2B1;COM2B0;-;-;WGM21;WGM20] …
TCCR2B [FOC2A;FOC2B;-;-;WGM22;CS22;CS21;CS20] …
TCNT2 = Counter, OCR2A = ComparMatch2A, OCR2B = ComparMatch2B
TIMSK2 [...-;OCIE2B;OCIE2A;TOIE2] …
TIFR2 [...-;OCF2B;OCF2A;TOV2] …
ASSR [-;EXCLK;AS2;TCN2UB;OCR2AUB;OCR2BUB;TCR2AUB;TCR2BUB]

Moi je suis nul pour les tuto, mais si avec cette base, quelqu'un veut s'y frotter...

UniseV:
J'aimerais vraiment que le soft Arduino permette 2 modes de compilation, avec et sans l'environnement Arduino, ça allègerait forcément les devs de certains projets.

-> AVR studio ou eclipse + toolchain avr-gcc ou notepad++ & makefile ...
Pas besoin de l'ide arduino si c'est pour faire du avr-c/c++ ...

Merci Skywodd,

Je crois bien que je ne vais pas tarder à m'y mettre à l'Arduino sans environnement Arduino...

Je viens de faire un point sur les timers, dans la datasheet, ils sont arrêtés tout les trois, par défaut.
Par curiosité, je viens de faire un programme qui interroge les registres timer dès le démarrage de la bête et sans aucune librairie... voici le résultat comenté :

____________________________________________
               Timer 0 :
TCCR0A : 11		(mode 3, fast PWM)
TCCR0B : 11		(prescaler clk/64)
TIMSK0 : 1		(Timer Over Inter)
____________________________________________
               Timer 1 :
TCCR1A : 1		(mode 1, PWM phase correct, 8-bit)
TCCR1B : 11		(prescaler clk/64)
TCCR1C : 0
TIMSK1 : 0
____________________________________________
               Timer 2 :
TCCR2A : 1		(mode 1, PWM phase correct)
TCCR2B : 100		(prescaler clk/256)
TIMSK2 : 0
____________________________________________

Le timer0 (environ 250KHz) déclenche une interruption lorsqu'il est plein.
Rien d'étonnant, l'interruption doit surement incrémenter un compteur qui permet au fonction millis() et micros() de renvoyer le temps total depuis l'allumage.

En revanche, le timer1 tourne à la même fréquence sans interruption et je ne sais pas pourquoi...
Le timer2 tourne lui à enciron 62,5KHz... et je ne sais pas pourquoi.

Le seul point rassurant, c'est que, à part l'interruption du timer0 (qui intervient à peu près tout les 1ms), rien ne "consomme" du proc.

Sev

UniseV:
Je crois bien que je ne vais pas tarder à m'y mettre à l'Arduino sans environnement Arduino...

On finit vite par apprécier de coder en avr-c, c'est tellement plus documenté et surtout il y a un standard derrière (la libc).

UniseV:
En revanche, le timer1 tourne à la même fréquence sans interruption et je ne sais pas pourquoi...
Le timer2 tourne lui à enciron 62,5KHz... et je ne sais pas pourquoi.

C'est normal, le processus de démarage du "core arduino" est le suivant :

internal_init()
setup()
while(1) -> loop()

internal_init() configure le Timer1 pour que millis() & co fonctionnent, mais (pré)configure aussi les autres timers pour la fonction analogWrite (PWM) qui ne fait rien d'autre que lancer la génération du signal PWM.

J'ai (je crois que je me répète, désolé... :D) déjà modifié la librairie dans l'idée de ne pas compiler l'ISR de réception afin de pouvoir moi-même gérer cette int. en gros, j'ai entouré la déclaration de l'isr dans la lib par un truc genre #ifdef / #endif. donc dans mon prog, il me suffit de déclarer #define PAS_D_ISR pour que l'isr ne soit pas compilée et que je puisse en faire une rien que pour moi. (PAS_D_ISR devient en fait une directive de compilation)

je n'ai pas trop testé, beaucoup de trucs sur le feu... mais je pense que ça doit marcher?

Mais dans cette idée, on pourrait utiliser un #define LIBERTE qui empêcherait la compilation de tout ces codes encombrants du core arduino, tout en restant dans l'IDE arduino, non? ainsi, pas de souci dû au timer0... mais bonjour le boulot de modifier toutes les libs, car le plus basique des programme fait quand-même intervenir un paquet de libs (j'avais estimé entre 500 et 900 octets de code en plus du mien)...