Aller au contenu

Messages recommandés

Posté(e)

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

Voici le câblage :

- 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 :

#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 :

#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 :
 

#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 appliquées au registre d'un port 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 :

// 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 :

URTouch ts; 

void setup() {
        ...
    ts.InitTouch(PORTRAIT); 
        ...
}
void loop() {

    if (ts.dataAvailable()) {
        if (ts.read()) {
            Touch_X = ts.getX();
            Touch_Y = ts.getY();
            ...
        }
    }
    ...
}



    

Posté(e) (modifié)

Salut,

Il y a 18 heures, electroremy a dit :

J'en suis arrivé à la conclusion que les instructions *reg &= ~bitmask et/ou *reg |= bitmask appliquées au registre d'un port 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 ?

Tu as bien sniffé, mais ce n'est pas exactement des changements d'état au sens glitch du terme. En fait les macros en C ne sont pas atomiques.

En clair tu as trois opérations : Lecture, modification, écriture.

Après lecture, si tu as une lib ou une interruption qui modifie un bit qui la concerne, lorsque toi tu vas appliquer l'écriture de tes bits (donc tout le port comme tu le souligne) tu va venir écraser ce bit par l'état du moment de la lecture.

Il te faut soit protéger les macros en les encadrant par une section critique (en gros disable interrupt), soit utiliser la méthode du PIN toggle si applicable.

A+

Modifié (le) par Kachidoki
  • J'aime 1
  • Merci ! 1
Posté(e)
Il y a 2 heures, Kachidoki a dit :

Après lecture, si tu as une lib ou une interruption qui modifie un bit qui la concerne, lorsque toi tu vas appliquer l'écriture de tes bits (donc tout le port comme tu le souligne) tu va venir écraser ce bit par l'état du moment de la lecture.

Tu m'as grillé 😁 - j'avais trouvé l'explication mais tu as répondu avant que je le fasse 😉

Mais je suis content, j'avais peur que le sujet n'intéresse pas grand monde !

 

Je vais juste donner un peu plus détails - ça pourra être utile pour les autres "Arduinistes" du forum 😉

 

Alors, dans le code de la librairie URTouch, ces deux lignes modifient un bit du port de façon plus rapide que digitalWrite()

*reg &= ~bitmask

ou encore

*reg |= bitmask

 

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

https://garretlab.web.fc2.com/en/arduino/inside/hardware/arduino/avr/cores/arduino/wiring_digital.c/digitalWrite.html

 

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 (voyez les lignes *out &= ~bit et *out |= bit)  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

Cette désactivation temporaire est réalisée par les lignes de code suivantes :

        uint8_t oldSREG = SREG;
        cli();
...
        SREG = oldSREG;

SREG est le registre dont un des bits autorise ou pas les interruptions ; il contient aussi d'autres bits ayant un rôle important

cli(); désactive les interruptions

En sauvegardant SREG, en désactivant les interruptions, puis en restaurant SREG, on peut travailler "proprement" avec le hardware car on remet le microcontrôleur de l'Arduino dans le même état qu'on l'a trouvé.

 

En effet, si les lignes de code

*reg &= ~bitmask

et

*reg |= bitmask

ne font qu'une seule ligne de C++, elles sont compilées en assembleur par plusieurs instructions qui se suivent

Comme l'a dit @Kachidoki : ce ne sont donc pas des fonctions "atomiques" qui s'execute d'un seul bloc.

C'est le "piège" du C++ : sa syntaxe peut être très compacte mais une petite ligne de C++ peut correspondre à un nombre d'instructions assembleur plus ou moins nombreuses. Dans certains cas, le C++ peut donner l'impression à tord d'avoir optimisé un programme en écrivant un code C++ plus court... qui aboutira au final à un code assembleur de taille identique 🤣 Là où c'est moins drôle, c'est que le C++ peut parfois faire passer pour atomique une instruction qui ne l'est pas.

 

Il est donc possible qu'une interruption interviennent en plein milieu de la série d'instruction assembleur correspondant à *reg &= ~bitmask ou *reg |= bitmask ; si le code de cette interruption modifie aussi les registres utilisés par le code on se retrouve avec un bug assez difficile à trouver.

Bref, l'optimisation faite par l'auteur de URTouch a été un peu trop loin 😅

 

Chose intétessante : la librairie que j'utilise pour l'afficheur ILI9341 utilise une autre librairie optimisée pour lire et écrire rapidement sur les broches : FastPin, qui est assez réputée.

https://github.com/JakesMD/FastPin/blob/main/src/FastPin.cpp

Voyons le code de FastPin :

#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; }

Même si FastPin est plus rapide que digitalWrite(), on voit bien que FastPin procéde prudemment en sauvegardant SREG, en désactivant les interruptions, et en restorant SREG.

 

A bientôt

Posté(e)

C'est un peu mon métier. 😁

C'est comme lorsque tu utilises des RTOS, il ne faut jamais négliger les mutex d'accès à une ressource partagée, et parfois même ne pas oublier de sauvegarder le contexte de la ressource (registres spécifiques utilisés par un périphérique partagé). Je ne parle même pas des sauvegardes de contexte étendu lorsqu'on utilise des tâches qui ont besoin d'une FPU par exemple (ça concerne plutôt les Cortex). C'est le type de code qui ne s'invente pas, il faut savoir que ça existe et comprendre pourquoi. Si on réfléchi à une fonction "unitairement", il est facile d'oublier de se protéger des accès concurrents.

Bien entendu, on limite toujours les sections critiques au juste nécessaire, sinon ça créé du jitter sur le traitement des interruptions.

Et ne pas oublier de se protéger contre les sections critiques imbriqués. 😉 Une fonction qui créé une section critique qui appelle une autre fonction qui elle-même créé une section critique. Si le nesting n'est pas géré, la première qui va libérer les interruptions va casser la section critique parente.

Ce genre de protection manque souvent dans les codes développés par des personnes qui ne sont pas forcément du métier, et on se retrouve facilement avec des libs communautaires bancales. Souvent ce genre de code marchotte dans certaines conditions, puis on ajoute une ligne de programme qui n'a rien à voir, ça change les timings de traitement et plus rien de fonctionne.

  • +1 1
Posté(e) (modifié)
Il y a 3 heures, Kachidoki a dit :

Ce genre de protection manque souvent dans les codes développés par des personnes qui ne sont pas forcément du métier, et on se retrouve facilement avec des libs communautaires bancales. Souvent ce genre de code marchotte dans certaines conditions, puis on ajoute une ligne de programme qui n'a rien à voir, ça change les timings de traitement et plus rien de fonctionne.

C'est exactement ça 😁

Avec l'Arduino on peut avoir des forks de lib qui fonctionnent très bien mais dès qu'on s'écarte un peu trop du cas d'usage classique ça ne fonctionne plus.

C'est parfois cruel, l'Arduino permet à des non-spécialistes de réaliser facilement un circuit programmable embarqué, et paf, d'un seul coup ils se retrouvent bloqués par un problème ayant un niveau de complexité beaucoup plus élevé, qui demande de mettre les mains dans le cambouis et faire du reverse engineering quand la doc ne suffit pas.

La courbe d'apprentissage est assez raide 😁

Cependant, certains sites sont très bien faits, comme https://www.locoduino.org/spip.php?rubrique14 qui aborde des notions assez pointue de façon plutôt accessible.

 

 

Modifié (le) par electroremy
Posté(e) (modifié)

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 :

	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 :

#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 :

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

		if(temp & 0x80) 
			sbi(P_DIN, B_DIN);
		 else 
			cbi(P_DIN, B_DIN);

par

		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é :

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

 

En réfléchissant au problème, quand on regarde la suite d'instructions suivante :

oldSREG = SREG;
cli();
*reg &= ~bitmask;
SREG = oldSREG;

il n'y a rien qui empêche une interruption d'être exécutée après oldSREG = SREG; mais avant cli();

mais si cette explication était bonne, le test que j'ai fait en ajoutant les digitalWrite(); sur les broches du port L aurait occasionné le bug ce qui n'est pas le cas

Modifié (le) par electroremy
Posté(e)

Hello,

J'ai pas tout regardé, mais y'a un truc qui me chiffonne :

pinMode(T_IRQ,  OUTPUT);

C'est bien le code côté Arduino là ? Du coup ça devrait pas être plutôt une entrée INPUT_PULLUP ?

Posté(e) (modifié)
Il y a 4 heures, Kachidoki a dit :

Hello,

J'ai pas tout regardé, mais y'a un truc qui me chiffonne :

pinMode(T_IRQ,  OUTPUT);

C'est bien le code côté Arduino là ? Du coup ça devrait pas être plutôt une entrée INPUT_PULLUP ?

Oui c'est bizarre... mais ça fonctionne... donc je n'y ai pas touché 😁

Révélation

Voici la régle d'or des dépanneurs TV (à l'époque des TV analogiques à tube cathodique) :

☠️ NE TOUCHEZ PAS A CE QUI FONCTIONNE ☠️

Beaucoup d'électroniciens ont fait plus de mal que de bien à une TV en panne car ils ont touchés aux réglages des circuits qui n'avaient pas de problème...

... genre une TV avec un problème de son, et après la tentative de réparation, il y a en plus un problème avec les couleurs 💩

Je ne suis pas l'auteur de la lib et il n'y a pas de documentation, juste un exemple de code qui montre comment s'en servir 😅

Voici comment on utilise la lib :

Dans le setup() du programme .INO, on appelle la fonction InitTouch()

Note : l'écran aura dû être calibré avant avec un programme, qui permet de déterminer les valeurs de touch_x_left, touch_x_right, touch_y_top(touch_y_bottom, j'ai une procédure dans mon INO qui permet de le faire et qui conserve les bonnes valeurs en EEPROM (dans la lib initiale, il fallait charger un sketch de calibration dans l'Arduino et noter les valeurs sur une feuille de papier pour les écrire en dur dans le code 😅)

Dans la boucle loop() du programme .INO, on appelle régulièrement la fonction dataAvailable()

Si elle renvoit une valeur vraie, alors on appelle la fonction read()

Dans le code, la broche IRQ est tantôt passée en INPUT ou en OUTPUT... il doit y avoir une raison (ou pas) à cela.

Il faudrait que je regarde dans la datasheet du contrôleur ILI9341 et aussi celle de l'écran ou au moins son schéma

Merci

A bientôt

Modifié (le) par electroremy

Créer un compte ou se connecter pour commenter

Vous devez être membre afin de pouvoir déposer un commentaire

Créer un compte

Créez un compte sur notre communauté. C’est facile !

Créer un nouveau compte

Se connecter

Vous avez déjà un compte ? Connectez-vous ici.

Connectez-vous maintenant
  • Sur cette page :   0 membre est en ligne

    • Aucun utilisateur enregistré regarde cette page.
  • YouTube / Les Imprimantes 3D .fr

×
×
  • Créer...