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

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)...

Tu définit où #define PAS_D_ISR ?
Il faut que ce soit dans le source HardwareSerial.cpp

Et dans ce cas, comment gères-tu la réception ? Tu fournis ta propre routine d'interruption ?

barbudor:
Tu définit où #define PAS_D_ISR ?
Il faut que ce soit dans le source HardwareSerial.cpp

Ben justement, c'était ma question... si ce que tu dis est vrai, alors mon truc ne marche pas. vu que je l'ai jamais testé... :grin:

barbudor:
Et dans ce cas, comment gères-tu la réception ? Tu fournis ta propre routine d'interruption ?

oui, sinon ça ne sert à rien de la désactiver dans le core... en fait, c'était pour de la réception DMX, où ça ne sert à rien de recevoir des données qui ne nous concerne pas dans la trame, donc désactiver la réception une fois qu'on est servi et laisser du temps pour le traitement de ces données (une réception série à 250Kb/s en continu, ça laisse pas beaucoup de temps entre deux octets...) et attendre le retour de début de trame. (c'est un protocole assez compliqué mais qui se gère très bien avec un serial.begin(250000)...)

Je pense que je n'irai malheureusement pas plus loin, voici mon doc sur GoogleDrive :

MemoCounter PDF
MemoCounter DOC

Il est un peu déstructuré mais je m'en sert pour faire des copier/coller de nom de registre ou de valeur, et je m'en sers aussi pour parametrer mes timers.
C'est un sorte de mémo, avec les essentiels mais il faut déjà avoir compris le fonctionnement général des timer.

Sev