Go Down

Topic: [Tests] Temps pour faire changer d'état une pin (niveau Haut/Bas) (Read 10369 times) previous topic - next topic

Super_Cinci

#15
Oct 30, 2011, 07:18 am Last Edit: Oct 30, 2011, 07:39 am by Super_Cinci Reason: 1
A chacun d'apprécier...

Pour rester dans le sujet, j'ai fait un petit code qui écrit et lit sur un port parallèle 4 bits + R/W + CLK. tout simple.

Code: [Select]

boucle :
  mode Write;
  présenter les données sur le port data;
  faire un pulse sur CLK;
  mode Read;
  faire un pulse sur CLK;
  récupérer les données sur le port data;


Le code en passant par des pinMode() et digitalWrite()/Read() :
Code: [Select]

void setup() {
 pinMode (8, OUTPUT);  // R/W : PB0
 pinMode (9, OUTPUT);  // clock : PB1
}

byte dumyIn;   // variable de lecture
byte dumyOut;  // variable d'écriture

void loop() {
// -----------------Write data---------
 pinMode (2, OUTPUT);  // data 0 : PD2
 pinMode (3, OUTPUT);  // data 1 : PD3
 pinMode (4, OUTPUT);  // data 2 : PD4
 pinMode (5, OUTPUT);  // data 3 : PD5
 digitalWrite (9, LOW);  // Write data
 digitalWrite (2, (dumyOut & 0x01));              // set data
 digitalWrite (3, (dumyOut & 0x02) >> 1);
 digitalWrite (4, (dumyOut & 0x04) >> 2);
 digitalWrite (5, (dumyOut & 0x08) >> 3);
 digitalWrite (8, HIGH);  // clock rising
 delay(2);
 digitalWrite (8, LOW);  // clock falling
// -----------------Read data---------
 pinMode (2, INPUT);
 pinMode (3, INPUT);
 pinMode (4, INPUT);
 pinMode (5, INPUT);
 digitalWrite (9, HIGH);
 digitalWrite (8, HIGH);  // clock rising
 delay(2);
 dumyIn = digitalRead (2);         // read data
 dumyIn |= digitalRead (3) << 1;
 dumyIn |= digitalRead (4) << 2;
 dumyIn |= digitalRead (5) << 3;
 digitalWrite (8, LOW);  // clock falling
 delay(2);
}

compilation : 1456 octets.

Maintenant, je remplace les fonctions Arduino par des accès aux registres :
Code: [Select]

void setup() {
 DDRB |= 0x03;   // R/W : PB0; clock : PB1
}

byte dumyIn;   // variable de lecture
byte dumyOut;  // variable d'écriture

void loop() {
// -----------------Write data---------
 DDRD |= 0x3C;  // data(3:0) output
 PORTB &= 0xFD; // Write data
 dumyOut &= 0x0F;    // mask dumyOut
 dumyOut << 2;
 PORTD &= 0xC3;      // set data = 0;
 PORTD |= dumyOut;    // set data;
 PORTB |= 0x01; // clock rising
 delay(2);
 PORTB &= 0xFE;  // clock falling
// -----------------Read data---------
 DDRD &= 0xC3;     // data(3:0) input
 PORTB |= 0x02;    // Read data
 PORTB |= 0x01; // clock rising
 delay(2);
 dumyIn = (PIND & 0x3C);
 dumyIn >> 2;
 PORTB &= 0xFE;  // clock falling
 delay(2);
}

compilation : 724 octets j'ai donc divisé par deux la taille de mon programme, et à mon avis (j'ai pas testé), il doit aller... 10 fois plus vite?

Même code registres, mais en remplaçant (je pensais simplifier le code) :
Code: [Select]

 dumyIn = (PIND & 0x3C);
 dumyIn >> 2;

par :
Code: [Select]

 dumyIn = (PIND & 0x3C) >> 2;

compilation : 736 octets : j'ai perdu 12 octets (donc 6 instructions ASM supplémentaires...), juste en réécrivant deux lignes en une seule! De plus, cela doit certainement influer sur la RAM en passant par une variable de passage pour calculer l'opération entre parenthèses...
Je remplace alors ce bout de code par :
Code: [Select]

 dumyIn = PIND;
 dumyIn &= 0x3C;
 dumyIn >> 2;

et reviens à ma compilation de 724 octets.

Conclusion, si on veut du programme rapide et léger en flash, on évitera les fonctions Arduino et les lignes à opérations multiples!

Je n'ai pas le temps de tester pour mesurer les signaux, mais j'essaierai... (un coup d'oscillo sur la pin R/W pour mesurer la période nous donnera le temps que prend la boucle Loop()).

skywodd

Bonjour,


Conclusion, si on veut du programme rapide et léger en flash, on évitera les fonctions Arduino et les lignes à opérations multiples!

Si tu veux vraiment optimiser ton programme passe directement sur du c++ avr-gcc sans le core arduino ;) et active l'optimisation lors de la compilation (chose que ne fait pas l'ide de mémoire ...).


Exemple, ton code SANS le core arduino :
Code: [Select]

#define F_CPU 16000000UL  // 16 MHz
#include <util/delay.h> // for _delay_ms
#include <avr/io.h> // for register

int main() { // fonction main (aka setup sous arduino)
DDRB |= 0x03;   // R/W : PB0; clock : PB1

uint8_t dumyIn = 0;   // variable de lecture
uint8_t dumyOut = 0;  // variable d'écriture // NB le type byte n'est rien d'autre qu'un unsigned char soit un uint8_t

while(1) { // boucle infini (aka loop sous arduino)
// -----------------Write data---------
  DDRD |= 0x3C;  // data(3:0) output
  PORTB &= 0xFD; // Write data
  dumyOut &= 0x0F;    // mask dumyOut
  dumyOut = dumyOut << 2; // l'instruction dumyOu << 2; seul ne fait rien, dumyOut = dumyOut << 2;  semble plus cohérent
  PORTD &= 0xC3;      // set data = 0;
  PORTD |= dumyOut;    // set data;
  PORTB |= 0x01; // clock rising
  _delay_ms(2);
  PORTB &= 0xFE;  // clock falling
 
// -----------------Read data---------
  DDRD &= 0xC3;     // data(3:0) input
  PORTB |= 0x02;    // Read data
  PORTB |= 0x01; // clock rising
  _delay_ms(2);
  dumyIn = (PIND & 0x3C);
  dumyIn = dumyIn >> 2; // l'instruction dumyIn >> 2; seul ne fait rien, dumyIn = dumyIn >> 2;  semble plus cohérent
  PORTB &= 0xFE;  // clock falling
  _delay_ms(2);
}
}


Et comme je suis pas radin le makefile pour compiler le tout sans se prendre la tête ;)
Code: [Select]

DEVICE=atmega328p
AVRDUDE = avrdude -c usbtiny -B 1 -p $(DEVICE)
COMPILE = avr-gcc -Wall -Os -I. -mmcu=$(DEVICE) -DF_CPU=16000000 -DDEBUG_LEVEL=0
OBJECTS = main.o

all: main.hex

.c.o:
$(COMPILE) -c $< -o $@

.S.o:
$(COMPILE) -x assembler-with-cpp -c $< -o $@

.c.s:
$(COMPILE) -S $< -o $@

flash: all
$(AVRDUDE) -U flash:w:main.hex

fuse:
$(AVRDUDE) -U hfuse:w:0xdd:m -U lfuse:w:0xe1:m

readcal:
$(AVRDUDE) -U calibration:r:/dev/stdout:i | head -1

clean:
rm -f main.hex main.lst main.obj main.cof main.list main.map main.eep.hex main.bin *.o main.s

main.bin: $(OBJECTS)
$(COMPILE) -o main.bin $(OBJECTS)

main.hex: main.bin
rm -f main.hex main.eep.hex
avr-objcopy -j .text -j .data -O ihex main.bin main.hex

disasm: main.bin
avr-objdump -d main.bin

cpp:
$(COMPILE) -E main.c


Et voila ce que donne la compilation :
Quote

C:\Users\skywodd\Desktop\tut>avr-size -C --mcu=atmega328p main.bin
AVR Memory Usage
----------------
Device: atmega328p

Program:      66 bytes (0.2% Full)
(.text + .data + .bootloader)

Data:          0 bytes (0.0% Full)
(.data + .bss + .noinit)


Conclusion : Si on veut un programme léger, rapide et optimisé on utilise avr-gcc SANS le core arduino ;)
(il n'y a pas que arduino dans la vie, avant arduino il fallait bien faire sans)
Des news, des tutos et plein de bonnes choses sur http://skyduino.wordpress.com !

Benvenuto

#17
Oct 30, 2011, 03:39 pm Last Edit: Oct 30, 2011, 04:34 pm by Benvenuto Reason: 1

Oui, mais si tu fais directement PORTB |= 0x04, par exemple, tu gagnes une instruction par rapport à PORTB |= (1 << 2), donc du temps... c'est surtout une question d'habitude... à l'école, on codait en ASM (dans le temps), c'était bien plus transparent...


Le code final ne sera pas forcément différent, parce que le compilateur se charge justement d'optimiser tout ça. Un compilateur qui ne convertirait pas un (1 << 2) en 0x04 ne serait pas très efficace, c'est justement le genre de choses qu'on attend de lui.

En examinant les listings assembleur produits par avr-gcc on peut voir exactement ce qui change dans le code assembleur généré.

Super_Cinci

je serais curieux de voir le résultat qui ne prend que 66 octets (donc une 30aine d'instructions ASM)... on diviserait alors la taille d'un prog par 20? hum! intéressant!

J'avais un doute sur dumyOut = dumyOut << 2; ou dumyOut << 2; , je n'ai rien trouvé à ce sujet...

Evidemment, je pense que la fonction delay() de l'arduino doit prendre un bon pourcentage de mes 724 octets... pour en plus ne pas être très précise.

Si j'avais le temps, je passerais le tout en assembleur, mais là, j'avoue que j'ai la flegme.

à suivre tout ça!

Super_Cinci

#19
Oct 31, 2011, 10:02 am Last Edit: Oct 31, 2011, 10:09 am by Super_Cinci Reason: 1
Pour aller un peu plus loin, j'ai fait les compilations suivantes :

bareMinimum : setup() et loop() vides : 450 octets,
avec "delay(2);" dans loop() : 640 octets, (+190 octets)
avec "delay(2); delay(3);" dans loop() : 652 octets, (+12 octets)
avec "delay(2); delay(3); delay(5);" dans loop() : 664 octets, (+12 octets)

un appel de fonction delay() prendrait alors 12 octets (6 word instructions?), donc la fonction delay() prendrait 178 octets.

Par ailleurs, (car du coup, je m'intéresse aux pertes de l'arduino core), quand on regarde de près Wiring.c, on voit que timer0 est largement sollicité pour déclencher le compteur pour millis() et micros() qui servent de base à la fonction delay(). Etant donné la trop faible précision annoncée pour ces fonctions, les cli() et les attouchements à SREG... ça donne envie de tout virer, pour écrire sa propre fonction delay().

Je comprends de mieux en mieux pourquoi plus on utilise les fonctions core arduino, plus on perd en précision... Ca donne à réfléchir, car moi, je n'utilise jamais directement millis() ou micros(), et j'aimerais bien utiliser le timer0 de temps en temps pour autre chose.

Pour en revenir aux registres, en utilisant bareMinimum, toujours, le poids des instructions suivantes :

Code: [Select]

DDRD |= 0x02;    // 2 octets
DDRD &= 0x02;    // 6 octets
DDRD = 0x02;    // 4 octets


Hum hum... je croyais que l'asm de l'atmega comprenait ces instructions en 1 seule WI.

Benvenuto

Pour les délais il y a la fonction _delay_ms() de avr-libc qui n'utilise que des cycles processeurs.

Pour l'utiliser, il suffit d'ajouter :
Code: [Select]
#include <util/delay.h>

skywodd

#21
Oct 31, 2011, 11:42 am Last Edit: Oct 31, 2011, 12:21 pm by skywodd Reason: 1

un appel de fonction delay() prendrait alors 12 octets (6 word instructions?), donc la fonction delay() prendrait 178 octets.

Configuration du timer0 + gestion de l'incrémentation en interne + divers modif/ajout fait au cours du temps, etc ...


Par ailleurs, (car du coup, je m'intéresse aux pertes de l'arduino core), quand on regarde de près Wiring.c, on voit que timer0 est largement sollicité pour déclencher le compteur pour millis() et micros() qui servent de base à la fonction delay(). Etant donné la trop faible précision annoncée pour ces fonctions, les cli() et les attouchements à SREG... ça donne envie de tout virer, pour écrire sa propre fonction delay().

Ou utiliser la fonction _delays_ms ;)
Il existe un "core avr", je ne me rappelle plus le nom mais il s'agissait d'une couche d'abstraction matériel "arduino like" optimisé à mort ...
Edit: La voici : http://www.makehackvoid.com/project/MHVLib


Je comprends de mieux en mieux pourquoi plus on utilise les fonctions core arduino, plus on perd en précision... Ca donne à réfléchir, car moi, je n'utilise jamais directement millis() ou micros(), et j'aimerais bien utiliser le timer0 de temps en temps pour autre chose.

Le but du core arduino c'est pas d'être précis mais d'être facile à utiliser ;)
Si tu veux vraiment tout optimiser, passe sur de l'assembleur mais dans ce cas tu n'est plus du tout dans l'optique arduino, mais dans l'optique dev de production.
Des news, des tutos et plein de bonnes choses sur http://skyduino.wordpress.com !

D4p0up

Bonjour à la communauté Fr,

Pour information, vous avez dans votre dossier d'installation Arduino, au chemin Arduino-xx\Hardware\Arduino\cores\arduino\ le source de l'OS Arduino. Le code de la fonction digitalWrite y est donné dans le fichier wiring_digital.c, recopié ci dessous pour info.

Cela permet de comprendre :


  • Le rapport de 10 : Digitalwrite contient pas moins de 4 appels de fonctions, 8 assignations de variables, 3 tests et deux écritures. Cela fait une vingtaine d'opérations en tout, le tout pour seulement changer une sortie.

  • Le delta sur les PWM: Il y a un test spécifique aux sorties PWM (commenté), et l'appel d'une fonction spécifique. cela explique le delta pour les PWM.



A noter également que l'utilisation des sorties 5 et 6 en PWM peut poser problème, car le Timer 0 auquel elles sont associées est utilisé pour les fonctions millis() et delay(). Cette précaution est rappelée dans la doc Arduino sur la fonction analogWrite();

Code: [Select]
void digitalWrite(uint8_t pin, uint8_t val)
{
uint8_t timer = digitalPinToTimer(pin);
uint8_t bit = digitalPinToBitMask(pin);
uint8_t port = digitalPinToPort(pin);
volatile uint8_t *out;

if (port == NOT_A_PIN) return;

// If the pin that support PWM output, we need to turn it off
// before doing a digital write.
if (timer != NOT_ON_TIMER) turnOffPWM(timer);

out = portOutputRegister(port);

if (val == LOW) {
uint8_t oldSREG = SREG;
                cli();
*out &= ~bit;
SREG = oldSREG;
} else {
uint8_t oldSREG = SREG;
                cli();
*out |= bit;
SREG = oldSREG;
}
}


Blog, DIY Electronics & Stuff - http://www.banson.fr

Super_Cinci

Bien vu, D4p0up!

une belle usine à gaz que voilà juste pour changer un bit!

Snootlab

Bonjour,

Le meilleur moyen pour connaitre le temps minimum de basculement d'une sortie, est d'une part de regarder les caractéristiques électriques de basculement dans la datasheet, et d'une autre, part, de regarder le tableau d'instructions en ASM à la fin de la datasheet. En effet, ce tableau à la particularité de donner le nombre de cycles d'horloges nécessaires pour une instruction asm donnée.
Si vous codez en C, les fichiers asm générés lors de la compilation vous permettront de voir en quelle suite d'instructions votre programme a été traduit.

Avec une simple multiplication, vous obtiendrez votre résultat.
Vous pouvez ensuite inclure directement de l'ASM dans l'IDE Arduino, et comparer les résultats à la compilation.

Bonne journée.

-----
Stéphane.
- Distributeur officiel Arduino - France

Go Up