IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Arduino Discussion :

Entrées / sorties : écriture d'un bit sans perturber les autres bits du même port


Sujet :

Arduino

  1. #1
    Membre chevronné Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    1 011
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 1 011
    Par défaut Entrées / sorties : écriture d'un bit sans perturber les autres bits du même port
    Bonjour,

    J'ai rencontré un problème qui m'a donné un peu de fil à retordre.

    J'avais adapté des librairies pour faire fonctionner ensemble, sur un Arduino UNO :
    - le shield Ethernet 2 avec une puce W5500
    - un écran LCD graphique avec une puce ILI9341
    - une dalle tactile avec une puce XPT2046

    - Le shield Ethernet 2 utilise le bus SPI
    - L'écran LCD graphique utilise le bus SPI, la librairie est une version optimisée de celle d'Adafruit pour exploiter au mieux la vitesse du hardware SPI
    - La dalle tactile est en SPI mais gérée en bitbanging, avec une version optimisée de la librairie URTouch

    Un circuit convertisseur 3.3V/5V est intercalé entre l'Arduino et l'écran tactile

    Je ne suis pas arrivé à gérer la dalle tactile avec le hardware SPI mais l'ensemble fonctionne très bien

    J'ai voulu adapter mon projet pour le faire fonctionner sur un arduino MEGA

    Le câblage et le code ont dû être modifiés car les broches SPI ne sont pas les mêmes sur l'UNO et le MEGA

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     
    #if defined(ARDUINO_AVR_UNO)
    	#define	ILI9341_CS_PIN	0 // (PD0)
    	#define	ILI9341_DC_PIN	9 // (PB1)
    	#define	ILI9341_RST_PIN	8 // (PB0)
    	//      ILI9341_MISO    12   (PB4) No choice, Mega Hardware SPI MISO - Also used by Ethernet Shield
    	//      ILI9341_MOSI    11   (PB3) No choice, Mega Hardware SPI MOSI - Also used by Ethernet Shield
    	//      ILI9341_CK      13   (PB5) No choice, Mega Hardware SPI CK   - Also used by Ethernet Shield
    #else
    	// ARDUINO MEGA
    	#define	ILI9341_CS_PIN	49 // (PL0)
    	#define	ILI9341_DC_PIN	48 // (PL1)
    	#define	ILI9341_RST_PIN	47 // (PL2)
    	//      ILI9341_MISO    50    (PB3) No choice, Mega Hardware SPI MISO - Also used by Ethernet Shield
    	//      ILI9341_MOSI    51    (PB2) No choice, Mega Hardware SPI MOSI - Also used by Ethernet Shield
    	//      ILI9341_CK      52    (PB1) No choice, Mega Hardware SPI CK   - Also used by Ethernet Shield
    #endif
    Sur l'Arduino MEGA, j'ai voulu câbler les I/O de l'écran tactile sur les I/O qui étaient à proximité de celles utilisées par l'écran graphique LCD

    Et rien ne fonctionnait...

    Comme j'avais beaucoup optimisé les librairie pour l'Arduino UNO, je me suis dit que j'avais certainement rendu celles-ci incompatibles avec l'Arduino MEGA

    Après pas mal de tests, je me suis rendu compte que c'était juste le choix des broches d'I/O qui étaient en cause ; pour résumer :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
     
    #if defined(ARDUINO_AVR_UNO)
    	#define	T_IRQ	5 // (PD5) 
    	#define	T_CS	2 // (PD2) 
    	#define	T_DOUT	7 // (PD7) MISO
    	#define	T_DIN	6 // (PD6) MOSI
    	#define	T_CLK	1 // (PD1) 
    #else
    	// ARDUINO MEGA
     
    	// FONCTIONNE CORRECTEMENT :
    	#define	T_IRQ	33 // (PC4)
    	#define	T_CS	34 // (PC3)
    	#define	T_DOUT	35 // (PC2)
    	#define	T_DIN	36 // (PC1)
    	#define	T_CLK	37 // (PC0)
     
    	// NE FONCTIONNE PAS :
    	// #define	T_IRQ	42 // (PL7)
    	// #define	T_CS	43 // (PL6)
    	// #define	T_DOUT	44 // (PL5)
    	// #define	T_DIN	45 // (PL4)
    	// #define	T_CLK	46 // (PL3)
     
    	// AVEC L'ECRAN TFT BRANCHE AINSI SUR L'ARDUINO MEGA :
    	// ILI9341_CS_PIN	49 (PL0)
    	// ILI9341_DC_PIN	48 (PL1) 
    	// ILI9341_RST_PIN	47 (PL2)
    	// ILI9341_MISO     50 (PB3) No choice, Mega Hardware SPI MISO
    	// ILI9341_MOSI     51 (PB2) No choice, Mega Hardware SPI MOSI
    	// ILI9341_CK       52 (PB1) No choice, Mega Hardware SPI CK
    #endif
    Ce qui pose problème, c'est d'utiliser des broches du même port (en l'occurrence, le port PL) pour :
    - les broches CS, DS et RST de l'écran tactile
    - les broches de la dalle tactile en SPI bitbanging

    Le code de la librairie URTouch utilise des macros assembleurs pour modifier un seul bit d'un port :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    #define cbi(reg, bitmask) *reg &= ~bitmask
    #define sbi(reg, bitmask) *reg |= bitmask
    #define rbi(reg, bitmask) ((*reg) & bitmask)
     
    #define pulse_high(reg, bitmask) sbi(reg, bitmask); cbi(reg, bitmask);
    #define pulse_low(reg, bitmask) cbi(reg, bitmask); sbi(reg, bitmask);
    On a tendance à l'oublier avec les langages "user friendly" comme Arduino : sur un microcontrôleur 8 bits, on n'écrit pas directement sur une broche d'entrée/sortie, mais on écrit un octet dans un port.

    J'en suis arrivé à la conclusion que les instructions *reg &= ~bitmask et/ou *reg |= bitmask pourraient engendrer des changements d'état parasites des bits qui ne sont pas modifiés, ce qui fait planter la puce ILI9341 de l'écran graphique.
    Mais je n'en suis pas sûr...
    Quel est votre avis ?


    J'ai d'abord pensé que la librairie qui gère l'ILI9341 avec le hardware SPI était aussi en cause...
    .. mais en faisant un test avec la librairie UTFT en bitbanging sur les mêmes broches le bug est aussi présent !

    Voici le code complet de URTouch.h :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    // DEFINE HERE THE PIN USED --------------------------------------------------------------
    #if defined(ARDUINO_AVR_UNO)
    	#define	T_IRQ	5 // (PD5) 
    	#define	T_CS	2 // (PD2) 
    	#define	T_DOUT	7 // (PD7) MISO
    	#define	T_DIN	6 // (PD6) MOSI
    	#define	T_CLK	1 // (PD1) 
    #else
    	// ARDUINO MEGA
     
    	// FONCTIONNE CORRECTEMENT :
    	#define	T_IRQ	33 // (PC4)
    	#define	T_CS	34 // (PC3)
    	#define	T_DOUT	35 // (PC2)
    	#define	T_DIN	36 // (PC1)
    	#define	T_CLK	37 // (PC0)
     
    	// NE FONCTIONNE PAS :
    	// #define	T_IRQ	42 // (PL7)
    	// #define	T_CS	43 // (PL6)
    	// #define	T_DOUT	44 // (PL5)
    	// #define	T_DIN	45 // (PL4)
    	// #define	T_CLK	46 // (PL3)
     
    	// AVEC L'ECRAN TFT BRANCHE AINSI SUR L'ARDUINO MEGA :
    	// ILI9341_CS_PIN	49 (PL0)
    	// ILI9341_DC_PIN	48 (PL1) 
    	// ILI9341_RST_PIN	47 (PL2)
    	// ILI9341_MISO     50 (PB3) No choice, Mega Hardware SPI MISO
    	// ILI9341_MOSI     51 (PB2) No choice, Mega Hardware SPI MOSI
    	// ILI9341_CK       52 (PB1) No choice, Mega Hardware SPI CK
    #endif
    // ---------------------------------------------------------------------------------------
     
    #if !defined(TFT_SIZE_WIDTH) 
    #define TFT_SIZE_WIDTH 240
    #endif
    #if !defined(TFT_SIZE_HEIGHT) 
    #define TFT_SIZE_HEIGHT 320 
    #endif
    #define PORTRAIT			0
    #define LANDSCAPE			1
     
    // Now calibration data is saved on EEPROM, and calibration software is included in the "setup menu" of the interface :-)
    // So you don't have to burn a separate calibration sketch and include calibration data in the INO file for each bord and each screen
    int	touch_x_left, touch_x_right, touch_y_top, touch_y_bottom;
     
     // Hardware specific defines 
    #define cbi(reg, bitmask) *reg &= ~bitmask
    #define sbi(reg, bitmask) *reg |= bitmask
    #define rbi(reg, bitmask) ((*reg) & bitmask)
    #define pulse_high(reg, bitmask) sbi(reg, bitmask); cbi(reg, bitmask);
    #define pulse_low(reg, bitmask) cbi(reg, bitmask); sbi(reg, bitmask);
    #define regtype volatile uint8_t
    #define regsize uint8_t
    #define PREC_LOW			1
    #define URTouch_Prec 102
     
    class URTouch {
    	public:
    		int16_t	TP_X ,TP_Y;
    		void	InitTouch(byte orientation);
    		bool	read();
    		bool	dataAvailable();
    		int16_t	getX();
    		int16_t	getY();
    		void	calibrateRead();
    		byte	FutureOrientation;
    		void	UpdateOrientation(); 
        private:
    		regtype *P_CLK, *P_CS, *P_DIN, *P_DOUT, *P_IRQ;
    		regsize B_CLK, B_CS, B_DIN, B_DOUT, B_IRQ;
    		int	disp_x_size, disp_y_size;
    		void	touch_WriteData(byte data);
    		word	touch_ReadData();
    		byte	orient;
    };
     
    void URTouch::touch_WriteData(byte data) {
    	byte temp;
    	temp=data;
    	cbi(P_CLK, B_CLK);
    	for(byte count=0; count<8; count++)	{
    		if(temp & 0x80)
    			sbi(P_DIN, B_DIN);
    		else
    			cbi(P_DIN, B_DIN);
    		temp = temp << 1; 
    		cbi(P_CLK, B_CLK);                
    		sbi(P_CLK, B_CLK);
    	}
    }
     
    word URTouch::touch_ReadData() {
    	word data = 0;
    	for(byte count=0; count<12; count++) {
    		data <<= 1;
    		sbi(P_CLK, B_CLK);
    		cbi(P_CLK, B_CLK);                
    		if (rbi(P_DOUT, B_DOUT)) data++;
    	}
    	return(data);
    }
     
    void URTouch::UpdateOrientation() {
    	orient = FutureOrientation;
    }
     
    void URTouch::InitTouch(byte orientation) {
    	FutureOrientation	    = orientation;
    	orient					= orientation;
    	disp_x_size = TFT_SIZE_WIDTH-1;
    	disp_y_size = TFT_SIZE_HEIGHT-1;
    	P_CLK	= portOutputRegister(digitalPinToPort(T_CLK));
    	B_CLK	= digitalPinToBitMask(T_CLK);
    	P_CS	= portOutputRegister(digitalPinToPort(T_CS));
    	B_CS	= digitalPinToBitMask(T_CS);
    	P_DIN	= portOutputRegister(digitalPinToPort(T_DIN));
    	B_DIN	= digitalPinToBitMask(T_DIN);
    	P_DOUT	= portInputRegister(digitalPinToPort(T_DOUT));
    	B_DOUT	= digitalPinToBitMask(T_DOUT);
    	P_IRQ	= portInputRegister(digitalPinToPort(T_IRQ));
    	B_IRQ	= digitalPinToBitMask(T_IRQ);
    	pinMode(T_CLK,  OUTPUT);
        pinMode(T_CS,   OUTPUT);
        pinMode(T_DIN,  OUTPUT);
        pinMode(T_DOUT, INPUT);
        pinMode(T_IRQ,  OUTPUT);
    	sbi(P_CS, B_CS);
    	sbi(P_CLK, B_CLK);
    	sbi(P_DIN, B_DIN);
    	sbi(P_IRQ, B_IRQ);
    }
     
    bool URTouch::read() {
    	unsigned long tx=0, temp_x=0;
    	unsigned long ty=0, temp_y=0;
    	unsigned long minx=99999, maxx=0;
    	unsigned long miny=99999, maxy=0;
    	byte datacount=0;
    	cbi(P_CS, B_CS);                    
    	pinMode(T_IRQ,  INPUT);
    	for (byte i=0; i<URTouch_Prec; i++) {
    		if (!rbi(P_IRQ, B_IRQ))	{
    			touch_WriteData(0x90);        
    			pulse_high(P_CLK, B_CLK);
    			temp_x = touch_ReadData();
    			if (!rbi(P_IRQ, B_IRQ))	{
    				touch_WriteData(0xD0);      
    				pulse_high(P_CLK, B_CLK);
    				temp_y = touch_ReadData();
    				if ((temp_x>0) and (temp_x<4096) and (temp_y>0) and (temp_y<4096)) {
    					tx += temp_x;
    					ty += temp_y;
    					if (temp_x<minx) minx = temp_x;
    					if (temp_x>maxx) maxx = temp_x;
    					if (temp_y<miny) miny = temp_y;
    					if (temp_y>maxy) maxy = temp_y;
    					datacount++;
    				}
    			}
    		}
    	}
    	pinMode(T_IRQ,  OUTPUT);
    	tx -= minx + maxx;
    	ty -= miny + maxy;
    	datacount -= 2;
    	sbi(P_CS, B_CS);                    
    	if ((datacount==(URTouch_Prec-2)) or (datacount==PREC_LOW)) {
    		if (orient == PORTRAIT)	{
    			TP_X = ty/datacount;
    			TP_Y = tx/datacount;
    		}	else	{
    			TP_X = tx/datacount;
    			TP_Y = ty/datacount;
    		}
    		return true;
    	}	else	{
    		return false;
    	}
    }
     
    bool URTouch::dataAvailable() {
    	bool avail;
    	pinMode(T_IRQ,  INPUT);
    	avail = !(rbi(P_IRQ, B_IRQ));
    	pinMode(T_IRQ,  OUTPUT);
    	return avail;
    }
     
    int16_t URTouch::getX() {
    	int c;
    	if (orient == PORTRAIT)	{
    		c = long(long(TP_X - touch_x_left) * (disp_x_size)) / long(touch_x_right - touch_x_left);
    	}	else	{
    		c = long(long(TP_X - touch_y_top) * (-disp_y_size)) / long(touch_y_bottom - touch_y_top) + long(disp_y_size);
    	}
    	return c;
    }
     
    int16_t URTouch::getY() {
    	int c;
    	if (orient == PORTRAIT)	{
    		c = long(long(TP_Y - touch_y_top) * (disp_y_size)) / long(touch_y_bottom - touch_y_top);
    	}	else	{
    		c = long(long(TP_Y - touch_x_left) * (disp_x_size)) / long(touch_x_right - touch_x_left);
    	}
    	return c;
    }
     
    void URTouch::calibrateRead() {
    	unsigned long tx=0;
    	unsigned long ty=0;
    	cbi(P_CS, B_CS);                    
    	touch_WriteData(0x90);        
    	pulse_high(P_CLK, B_CLK);
    	tx=touch_ReadData();
    	touch_WriteData(0xD0);      
    	pulse_high(P_CLK, B_CLK);
    	ty=touch_ReadData();
    	sbi(P_CS, B_CS);                    
    	TP_X=ty;
    	TP_Y=tx;
    }
    Voici comment le code est utilisé dans le projet :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     
    URTouch ts; 
     
    void setup() {
            ...
    	ts.InitTouch(PORTRAIT); 
            ...
    }
    void loop() {
     
    	if (ts.dataAvailable()) {
    		if (ts.read()) {
    			Touch_X = ts.getX();
    			Touch_Y = ts.getY();
    			...
    		}
    	}
    	...
    }
    Quand deux personnes échangent un euro, chacun repart avec un euro.
    Quand deux personnes échangent une idée, chacun repart avec deux idées.

  2. #2
    Membre Expert
    Profil pro
    Inscrit en
    Septembre 2010
    Messages
    1 620
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France

    Informations forums :
    Inscription : Septembre 2010
    Messages : 1 620
    Par défaut
    tu as bien défini une broche "Chip select" pour chaque élément du bus SPI et elle sont correctement gérées? (seul le CS de l'écran est actif quand tu communiques avec l'écran, idem quand tu communiques avec la dalle ou le shield ethernet?

  3. #3
    Membre chevronné Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    1 011
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 1 011
    Par défaut
    Citation Envoyé par umfred Voir le message
    tu as bien défini une broche "Chip select" pour chaque élément du bus SPI et elle sont correctement gérées? (seul le CS de l'écran est actif quand tu communiques avec l'écran, idem quand tu communiques avec la dalle ou le shield ethernet?
    Oui bien sûr, chaque périphérique SPI a son CS.

    Sinon ça ne fonctionnerait pas du tout ou il y aurai d'autres bugs ce qui n'est pas le cas.

    Tout fonctionne correctement... sauf quand les broches de la dalle tactile sont sur le même port que les broches CS, DC et RST de l'écran LCD graphique. Dans ce cas de figure c'est très net : la première instruction qui tente de communiquer avec la dalle tactile fait planter l'écran LCD graphique.

    Je soupçonne un bref passage à l'état haute impédance du port qui déclenche un RESET de l'écran.
    Quand deux personnes échangent un euro, chacun repart avec un euro.
    Quand deux personnes échangent une idée, chacun repart avec deux idées.

  4. #4
    Membre chevronné Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    1 011
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 1 011
    Par défaut
    A priori il ne s'agit du problème Read-Modify-Write décrit ici :

    https://download.mikroe.com/document...c/help/rmw.htm

    En effet, le code utilise bien le registre de sortie (écriture) du port
    Quand deux personnes échangent un euro, chacun repart avec un euro.
    Quand deux personnes échangent une idée, chacun repart avec deux idées.

  5. #5
    Membre chevronné Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    1 011
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 1 011
    Par défaut
    La librairie URTouch utilise des macros qui sont plus rapides que digitalWrite()

    ainsi que

    On peut découvrir la complexité de digitalWrite() ici :

    https://garretlab.web.fc2.com/en/ard...italWrite.html

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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);
     
            uint8_t oldSREG = SREG;
            cli();
     
            if (val == LOW) {
                    *out &= ~bit;
            } else {
                    *out |= bit;
            }
     
            SREG = oldSREG;
    }
    Chose intéressante, digitalWrite() fait la même chose que les macros de URTouch mais avec plus de précautions...

    ... en particulier, digitalWrite() désactive temporairement les interruptions avant d'exectuer les instructions qui modifient le registre de sortie

    en effet, si les lignes de code et aboutissent à plusieurs intructions machine, elles peuvent être interrompues

    L'optimisation de URTouch a peut être été trop loin... ce qui explique qu'elle occasione des bugs avec d'autres librairies quand certaines combinaisons de broches sont employées...

    Ma librairie ILI9341 utilise FastPin()... jetons un oeil :

    https://github.com/JakesMD/FastPin/b...rc/FastPin.cpp

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    #include "FastPin.h"
     
    FastWritePin::FastWritePin(byte pin) { _pin = pin; }
     
    void FastWritePin::begin(bool initialVal) {
        pinMode(_pin, OUTPUT);
        digitalWrite(_pin, initialVal);  // Turns off PWM timers.
     
        byte port = digitalPinToPort(_pin);
        _bitMask = digitalPinToBitMask(_pin);
        _outputRegister = port != NOT_A_PIN ? portOutputRegister(port) : NULL;
    }
     
    void FastWritePin::write(bool val) {
        if (_outputRegister == NULL) return;
     
        byte oldSREG = SREG;
        cli();
     
        if (val == LOW) {
            *_outputRegister &= ~_bitMask;
        } else {
            *_outputRegister |= _bitMask;
        }
     
        SREG = oldSREG;
    }
     
    FastReadPin::FastReadPin(byte pin) { _pin = pin; }
     
    void FastReadPin::begin(bool pullup) {
        if (pullup) {
            pinMode(_pin, INPUT_PULLUP);
        } else {
            pinMode(_pin, INPUT);
        }
        digitalRead(_pin);  // Turns off PWM timers.
     
        byte port = digitalPinToPort(_pin);
        _bitMask = digitalPinToBitMask(_pin);
        _inputRegister = port != NOT_A_PIN ? portInputRegister(port) : NULL;
    }
     
    bool FastReadPin::read() { return (_inputRegister != NULL && *_inputRegister & _bitMask) ? HIGH : LOW; }
    Là aussi, les interruptions sont temporairement désactivtées

    A bientôt
    Quand deux personnes échangent un euro, chacun repart avec un euro.
    Quand deux personnes échangent une idée, chacun repart avec deux idées.

  6. #6
    Membre chevronné Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    1 011
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 1 011
    Par défaut
    Bonjour,



    J'ai modifié la bibliothèque URTouch pour désactiver temporairement les interruptions mais ça ne résoud pas le problème

    - fonctionnement correct si les broches du port PL ne sont pas utilisées pour la dalle tactile

    - si les broches du port PL sont utilisées pour la dalle tactile, alors l'écran graphique LCD ne fonctionne plus (mais tout le reste du code, y compris le shield Ethernet qui utilise le même port SPI, fonctionne très bien)


    Pour éliminer tout problème venant d'ailleurs (problème de hardware, ou un autre bug ailleurs dans le projet), j'ai repris le code qui fonctionne, et ajouté manuellement des instructions qui écrivent sur les broches libres du port PL, aux endroits où je communique avec la dalle tactile :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    	digitalWrite(42, HIGH);
    	digitalWrite(43, HIGH);
    	digitalWrite(44, HIGH);
    	digitalWrite(45, HIGH);
    	digitalWrite(46, HIGH);
    	digitalWrite(42, LOW);
    	digitalWrite(43, LOW);
    	digitalWrite(44, LOW);
    	digitalWrite(45, LOW);
    	digitalWrite(46, LOW);

    Tout fonctionne correctement

    => C'est bien le code URTouch qui est en cause. Les corrections que j'ai apporté ne sont pas bonnes...

    ... mais je ne comprends pas pourquoi

    J'ai modifié les macros de la façon suivante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #define TOUCH_SAVE_PCR 1
     
    #if (TOUCH_SAVE_PCR)
    	#define cbi(reg, bitmask) oldSREG = SREG; cli(); *reg &= ~bitmask; SREG = oldSREG
    	#define sbi(reg, bitmask) oldSREG = SREG; cli(); *reg |= bitmask; SREG = oldSREG
    	#define rbi(reg, bitmask) ((*reg) & bitmask)
    	#define pulse_high(reg, bitmask) oldSREG = SREG; cli(); *reg |= bitmask; *reg &= ~bitmask; SREG = oldSREG
    	#define pulse_low(reg, bitmask) oldSREG = SREG; cli(); *reg &= ~bitmask; *reg |= bitmask; SREG = oldSREG
    #else
    	#define cbi(reg, bitmask) *reg &= ~bitmask
    	#define sbi(reg, bitmask) *reg |= bitmask
    	#define rbi(reg, bitmask) ((*reg) & bitmask)
    	#define pulse_high(reg, bitmask) sbi(reg, bitmask); cbi(reg, bitmask)
    	#define pulse_low(reg, bitmask) cbi(reg, bitmask); sbi(reg, bitmask)
    #endif
    J'ai bien sûr dû ajouter la déclaration de la variable temporaire oldSREG dans les fonctions qui utilisent les macros - exemple ici :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    void URTouch::touch_WriteData(byte data) {
    	byte temp;
    	temp=data;
    	#if TOUCH_SAVE_PCR
    		byte oldSREG;
    	#endif
    	cbi(P_CLK, B_CLK);
    	for(byte count=0; count<8; count++)	{
    		if(temp & 0x80) {
    			sbi(P_DIN, B_DIN);
    		} else {
    			cbi(P_DIN, B_DIN);
    		}
    		temp = temp << 1; 
    		cbi(P_CLK, B_CLK);                
    		sbi(P_CLK, B_CLK);
    	}
    }
    NB : impossible de déclarer oldSREG dans le code d'une macro, car si une fonction utiliser plusieurs fois la macro, la variable oldSREG est déclarée plusieurs fois, le code n'est pas conforme la compilation échoue.

    J'ai aussi dû remplacer
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    if(temp & 0x80) 
    			sbi(P_DIN, B_DIN);
    		 else 
    			cbi(P_DIN, B_DIN);
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    if(temp & 0x80) {
    			sbi(P_DIN, B_DIN);
    		} else {
    			cbi(P_DIN, B_DIN);
    		}
    Comme avec ma modification les macros sbi() et cbi() ont maintenant plusieurs instructions séparées par des ";", les accolades sont nécessaires dans le bloc if/else



    Voici le code complet de URTouch modifié :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    #pragma once
     
      // DEFINE HERE THE PIN USED --------------------------------------------------------------
    #if defined(ARDUINO_AVR_UNO)
    	#define	T_IRQ	5 // (PD5) 
    	#define	T_CS	2 // (PD2) 
    	#define	T_DOUT	7 // (PD7) MISO
    	#define	T_DIN	6 // (PD6) MOSI
    	#define	T_CLK	1 // (PD1) 
    #else
    	// ARDUINO MEGA
     
    	// FONCTIONNE CORRECTEMENT :
    	// #define	T_IRQ	33 // (PC4)
    	// #define	T_CS	34 // (PC3)
    	// #define	T_DOUT	35 // (PC2) MISO
    	// #define	T_DIN	36 // (PC1) MOSI
    	// #define	T_CLK	37 // (PC0)
     
    	// NE FONCTIONNE PAS :
    	#define	T_IRQ	42 // (PL7)
    	#define	T_CS	43 // (PL6)
    	#define	T_DOUT	44 // (PL5) MISO
    	#define	T_DIN	45 // (PL4) MOSI
    	#define	T_CLK	46 // (PL3)
     
    	// AVEC L'ECRAN TFT BRANCHE AINSI SUR L'ARDUINO MEGA :
    	// ILI9341_CS_PIN	49 (PL0)
    	// ILI9341_DC_PIN	48 (PL1) 
    	// ILI9341_RST_PIN	47 (PL2)
    	// ILI9341_MISO     50 (PB3) No choice, Mega Hardware SPI MISO
    	// ILI9341_MOSI     51 (PB2) No choice, Mega Hardware SPI MOSI
    	// ILI9341_CK       52 (PB1) No choice, Mega Hardware SPI CK
    #endif
    // ---------------------------------------------------------------------------------------
     
    #if !defined(TFT_SIZE_WIDTH) 
    #define TFT_SIZE_WIDTH 240
    #endif
    #if !defined(TFT_SIZE_HEIGHT) 
    #define TFT_SIZE_HEIGHT 320 
    #endif
    #define PORTRAIT			0
    #define LANDSCAPE			1
     
    // Now calibration data is saved on EEPROM, and calibration software is included in the "setup menu" of the interface :-)
    // So you don't have to burn a separate calibration sketch and include calibration data in the INO file for each bord and each screen
    int	touch_x_left, touch_x_right, touch_y_top, touch_y_bottom;
     
     // Hardware specific defines 
     
    #define TOUCH_SAVE_PCR 1
     
    #if (TOUCH_SAVE_PCR)
    	#define cbi(reg, bitmask) oldSREG = SREG; cli(); *reg &= ~bitmask; SREG = oldSREG
    	#define sbi(reg, bitmask) oldSREG = SREG; cli(); *reg |= bitmask; SREG = oldSREG
    	#define rbi(reg, bitmask) ((*reg) & bitmask)
    	#define pulse_high(reg, bitmask) oldSREG = SREG; cli(); *reg |= bitmask; *reg &= ~bitmask; SREG = oldSREG
    	#define pulse_low(reg, bitmask) oldSREG = SREG; cli(); *reg &= ~bitmask; *reg |= bitmask; SREG = oldSREG
    #else
    	#define cbi(reg, bitmask) *reg &= ~bitmask
    	#define sbi(reg, bitmask) *reg |= bitmask
    	#define rbi(reg, bitmask) ((*reg) & bitmask)
    	#define pulse_high(reg, bitmask) sbi(reg, bitmask); cbi(reg, bitmask)
    	#define pulse_low(reg, bitmask) cbi(reg, bitmask); sbi(reg, bitmask)
    #endif
     
     
    #define regtype volatile uint8_t
    #define regsize uint8_t
    #define PREC_LOW			1
    #define URTouch_Prec 102
     
    class URTouch {
    	public:
    		int16_t	TP_X ,TP_Y;
    		void	InitTouch(byte orientation);
    		bool	read();
    		bool	dataAvailable();
    		int16_t	getX();
    		int16_t	getY();
    		void	calibrateRead();
    		byte	FutureOrientation;
    		void	UpdateOrientation(); 
        private:
    		regtype *P_CLK, *P_CS, *P_DIN, *P_DOUT, *P_IRQ;
    		regsize B_CLK, B_CS, B_DIN, B_DOUT, B_IRQ;
    		int	disp_x_size, disp_y_size;
    		void	touch_WriteData(byte data);
    		word	touch_ReadData();
    		byte	orient;
    };
     
    void URTouch::touch_WriteData(byte data) {
    	byte temp;
    	temp=data;
    	#if TOUCH_SAVE_PCR
    		byte oldSREG;
    	#endif
    	cbi(P_CLK, B_CLK);
    	for(byte count=0; count<8; count++)	{
    		if(temp & 0x80) {
    			sbi(P_DIN, B_DIN);
    		} else {
    			cbi(P_DIN, B_DIN);
    		}
    		temp = temp << 1; 
    		cbi(P_CLK, B_CLK);                
    		sbi(P_CLK, B_CLK);
    	}
    }
     
    word URTouch::touch_ReadData() {
    	word data = 0;
    	#if TOUCH_SAVE_PCR
    		byte oldSREG;
    	#endif
    	for(byte count=0; count<12; count++) {
    		data <<= 1;
    		sbi(P_CLK, B_CLK);
    		cbi(P_CLK, B_CLK);                
    		if (rbi(P_DOUT, B_DOUT)) data++;
    	}
    	return(data);
    }
     
    void URTouch::UpdateOrientation() {
    	orient = FutureOrientation;
    }
     
    void URTouch::InitTouch(byte orientation) {
    	FutureOrientation	    = orientation;
    	orient					= orientation;
    	disp_x_size = TFT_SIZE_WIDTH-1;
    	disp_y_size = TFT_SIZE_HEIGHT-1;
    	P_CLK	= portOutputRegister(digitalPinToPort(T_CLK));
    	B_CLK	= digitalPinToBitMask(T_CLK);
    	P_CS	= portOutputRegister(digitalPinToPort(T_CS));
    	B_CS	= digitalPinToBitMask(T_CS);
    	P_DIN	= portOutputRegister(digitalPinToPort(T_DIN));
    	B_DIN	= digitalPinToBitMask(T_DIN);
    	P_DOUT	= portInputRegister(digitalPinToPort(T_DOUT));
    	B_DOUT	= digitalPinToBitMask(T_DOUT);
    	P_IRQ	= portInputRegister(digitalPinToPort(T_IRQ));
    	B_IRQ	= digitalPinToBitMask(T_IRQ);
    	pinMode(T_CLK,  OUTPUT);
        pinMode(T_CS,   OUTPUT);
        pinMode(T_DIN,  OUTPUT);
        pinMode(T_DOUT, INPUT);
        pinMode(T_IRQ,  OUTPUT);
    	#if TOUCH_SAVE_PCR
    		byte oldSREG;
    	#endif
    	sbi(P_CS, B_CS);
    	sbi(P_CLK, B_CLK);
    	sbi(P_DIN, B_DIN);
    	sbi(P_IRQ, B_IRQ);
    }
     
    bool URTouch::read() {
    	unsigned long tx=0, temp_x=0;
    	unsigned long ty=0, temp_y=0;
    	unsigned long minx=99999, maxx=0;
    	unsigned long miny=99999, maxy=0;
    	byte datacount=0;
    	#if TOUCH_SAVE_PCR
    		byte oldSREG;
    	#endif
    	cbi(P_CS, B_CS);                    
    	pinMode(T_IRQ,  INPUT);
    	for (byte i=0; i<URTouch_Prec; i++) {
    		if (!rbi(P_IRQ, B_IRQ))	{
    			touch_WriteData(0x90);        
    			pulse_high(P_CLK, B_CLK);
    			temp_x = touch_ReadData();
    			if (!rbi(P_IRQ, B_IRQ))	{
    				touch_WriteData(0xD0);      
    				pulse_high(P_CLK, B_CLK);
    				temp_y = touch_ReadData();
    				if ((temp_x>0) and (temp_x<4096) and (temp_y>0) and (temp_y<4096)) {
    					tx += temp_x;
    					ty += temp_y;
    					if (temp_x<minx) minx = temp_x;
    					if (temp_x>maxx) maxx = temp_x;
    					if (temp_y<miny) miny = temp_y;
    					if (temp_y>maxy) maxy = temp_y;
    					datacount++;
    				}
    			}
    		}
    	}
    	pinMode(T_IRQ,  OUTPUT);
    	tx -= minx + maxx;
    	ty -= miny + maxy;
    	datacount -= 2;
    	sbi(P_CS, B_CS);                    
    	if ((datacount==(URTouch_Prec-2)) or (datacount==PREC_LOW)) {
    		if (orient == PORTRAIT)	{
    			TP_X = ty/datacount;
    			TP_Y = tx/datacount;
    		}	else	{
    			TP_X = tx/datacount;
    			TP_Y = ty/datacount;
    		}
    		return true;
    	}	else	{
    		return false;
    	}
    }
     
    bool URTouch::dataAvailable() {
    	bool avail;
    	pinMode(T_IRQ,  INPUT);
    	avail = !(rbi(P_IRQ, B_IRQ));
    	pinMode(T_IRQ,  OUTPUT);
    	return avail;
    }
     
    int16_t URTouch::getX() {
    	int c;
    	if (orient == PORTRAIT)	{
    		c = long(long(TP_X - touch_x_left) * (disp_x_size)) / long(touch_x_right - touch_x_left);
    	}	else	{
    		c = long(long(TP_X - touch_y_top) * (-disp_y_size)) / long(touch_y_bottom - touch_y_top) + long(disp_y_size);
    	}
    	return c;
    }
     
    int16_t URTouch::getY() {
    	int c;
    	if (orient == PORTRAIT)	{
    		c = long(long(TP_Y - touch_y_top) * (disp_y_size)) / long(touch_y_bottom - touch_y_top);
    	}	else	{
    		c = long(long(TP_Y - touch_x_left) * (disp_x_size)) / long(touch_x_right - touch_x_left);
    	}
    	return c;
    }
     
    void URTouch::calibrateRead() {
    	unsigned long tx=0;
    	unsigned long ty=0;
    	#if TOUCH_SAVE_PCR
    		byte oldSREG;
    	#endif
    	cbi(P_CS, B_CS);                    
    	touch_WriteData(0x90);        
    	pulse_high(P_CLK, B_CLK);
    	tx=touch_ReadData();
    	touch_WriteData(0xD0);      
    	pulse_high(P_CLK, B_CLK);
    	ty=touch_ReadData();
    	sbi(P_CS, B_CS);                    
    	TP_X=ty;
    	TP_Y=tx;
    }
    Remarque importe : ce code fait fonctionner correctement la dalle tactile, dans tous les cas j'arrive bien à détecter un appui sur l'écran tactile et à lire les coordonnées. D'ailleurs l'ensemble du projet fonctionne correctement, à condition de choisir des broches autres que celles du port PL pour la dalle tactile. A priori, mes modifications n'ont pas ajouté de bug supplémentaire.

    Le seul bug qui se produit, c'est quand on utilise les I/O du port PL pour la dalle tactile, cela empêche l'écran LCD graphique (dont les broches CS, RS et RST utilisent aussi le port PL) de fonctionner



    Les procédures étant courtes et avec peu de variables, je suis à peu près certain que le compilateur ne va pas utiliser la RAM mais un registre pour la variable oldSREG ; les opérations d'affectation seront donc rapides (surtout que les AVR ont de nombreux registres) - mais je n'en suis pas sûr.

    Je n'ai pas poussé mes investigations jusqu'à examiner l'exécutable généré - en fait... je n'ai jamais fait cette manip avec l'Arduino mais c'est peut être l'occasion d'essayer

    Si le problème vient de là, il faudra alors que j'utilise à la place des macros des fonctions INLINE ; la déclaration de la variable oldSREG sera dans le code des fonctions, ce sera une variable locale, avec l'attribut INLINE le compilateur va éviter tout seul les doubles déclarations.

    A bientôt
    Quand deux personnes échangent un euro, chacun repart avec un euro.
    Quand deux personnes échangent une idée, chacun repart avec deux idées.

  7. #7
    Membre Expert
    Femme Profil pro
    ..
    Inscrit en
    Décembre 2019
    Messages
    739
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 96
    Localisation : Autre

    Informations professionnelles :
    Activité : ..

    Informations forums :
    Inscription : Décembre 2019
    Messages : 739
    Par défaut
    Salut,

    J'ai lu en diagonale,
    mais ce serait bien d'identifier quel pin du port L pose problème et vérifier s'il a des fonctionnalités partagées actives.
    Ensuite, la fonction macro pulse_high() (la low() je ne la vois pas utilisée dans le code), je me demande si c'est pas trop rapide pour avoir un signal franc et détectable de l'autre côté.
    Pour tes autres macros et trains d'instructions, tu peux écrire macro(X, Y) do {auto backup=REG; bla bla bla avec X et Y; REG= backup;} while(0), et c'est compatible avec "if" et tout.
    Après, si ça fonctionne avec les digitalWrite() et consorts, une possibilité à moindre effort serait de modifier les appels et définitions de tes macros, je donne un exemple pour sbi() :
    sbi(P_DIN, B_DIN) devient sbi(T_DIN, P_DIN, B_DIN)
    sbi(reg, bitmask) devient sbi(pin, reg, bitmask)
    dans les définitions d'origines, seules les signatures changent en fait:
    sbi(pin, reg, bitmask) *reg|= bitmask
    ...
    quant aux versions customs, elles deviennent :
    cbi(pin, reg, bitmask) digitalWrite(pin, LOW)
    sbi(pin, reg, bitmask) digitalWrite(pin, HIGH)
    rbi(pin, reg, bitmask) (digitalRead(pin) == HIGH)
    pulse_high(pin, reg, bitmask) do {sbi(pin, reg, bitmask); cbi(pin, reg, bitmask);} while(0)

    Voilà pour les grandes lignes.

  8. #8
    Membre Expert

    Homme Profil pro
    Directeur de projet
    Inscrit en
    Mai 2013
    Messages
    1 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Directeur de projet
    Secteur : Service public

    Informations forums :
    Inscription : Mai 2013
    Messages : 1 717
    Par défaut
    Bonjour,

    J'ai fait une lecture transversale et je me demande si le compilateur ne râle pas un peu quand on fait rentrer un long dans un int (voir URTouch::getX ou Y). D'autant que le typage du résultat en long est superfétatoire.

    Ajout
    URTouche::read uitilise des 32 bits pour minx, maxx, miny, maxy alors que le code montre qu'ils doivent rester dans ]0, 4096[. Des initialisations à 4096 et 0 suffisent donc largement. Un uint16_t suffit alors pour ces variables et en conséquence pour temp_x et temp_y (c'est du reste le type du retour de la fonction touvh_ReadData, ce qui serait plus visible si word était remplacé par uint16_t). Travailler avec des formats inférieurs fait mécaniquement gagner du temps sur des machines 8 bits.

    Salutations
    Ever tried. Ever failed. No matter. Try Again. Fail again. Fail better. (Samuel Beckett)

Discussions similaires

  1. Réponses: 2
    Dernier message: 04/06/2010, 16h04
  2. ouvrir un Dialog sans bloquer les autres
    Par ____22 dans le forum MFC
    Réponses: 1
    Dernier message: 17/07/2009, 15h13
  3. Réponses: 4
    Dernier message: 16/01/2008, 21h39
  4. Div qui perturbe les autres
    Par Garra dans le forum Mise en page CSS
    Réponses: 3
    Dernier message: 02/05/2007, 18h30
  5. Afficher un message sans bloquer les autres traitements??
    Par Ben_Le_Cool dans le forum Langage
    Réponses: 7
    Dernier message: 13/10/2005, 01h21

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo