Utiliser les 2 SPI d'un ESP32-wroom

Bonjour à tous,

je veux ajouter un lecteur SD à mon système monté sur ESP32-Wroom qui utilise déjà le port SPI pour un panneau 4x8x8 MAX7219.
N'ayant pas cherché - ni trouvé, évidemment - comment changer les broches du lecteur SD, j'ai opté pour le déménagement du panneau MAX vers le second set de broches SPI du micro-contrôleur.

Les broches « par défaut » du port SPI de ESP32 sont:

#define broche_CLK          18 //GPIO-18  ... SPI microSD / broche no 30 -- vert       // noir => GND   blanc => 3.3V
#define broche_OUT          19 //GPIO-19  ... SPI microSD / broche no 31 -- gris
#define broche_DIN          23 //GPIO-23  ... SPI microSD / broche no 37 -- violet
#define broche_CS           5  //GPIO-05  ... SPI microSD / broche no 29 -- bleu

Les broches du second port SPI sont:

#define broch2_CLK         14 //GPIO-14  ... SPI écran / broche no 12  -- vert
#define broch2_OUT        12 //GPIO-12  ... SPI écran / broche no 13  -- MISO -> non-connecté
#define broch2_DIN         13 //GPIO-13  ... SPI écran / broche no 15  -- MOSI -> blanc
#define broch2_CS           15 //GPIO-15  ... SPI écran / broche no 23  -- rouge

Telles que présentées par Ruis Santos

Tout semble bien expliqué dans la page sus-mentionnée, mais je me mêle les pinceaux quand même. L'exemple donné à la page ci-haut est répété sur gitHub
Voici le code de l'exemple pour vous exempter de chercher :



/* The ESP32 has four SPi buses, however as of right now only two of
 * them are available to use, HSPI and VSPI. Simply using the SPI API
 * as illustrated in Arduino examples will use VSPI, leaving HSPI unused.
 *
 * However if we simply initialize two instance of the SPI class for both
 * of these buses both can be used. However when just using these the Arduino
 * way only will actually be outputting at a time.
 *
 * Logic analyzer capture is in the same folder as this example as
 * "multiple_bus_output.png"
 *
 * created 30/04/2018 by Alistair Symonds
 */
#include <SPI.h>

// Define ALTERNATE_PINS to use non-standard GPIO pins for SPI bus

#ifdef ALTERNATE_PINS
#define VSPI_MISO 2
#define VSPI_MOSI 4
#define VSPI_SCLK 0
#define VSPI_SS   33

#define HSPI_MISO 26
#define HSPI_MOSI 27
#define HSPI_SCLK 25
#define HSPI_SS   32
#else
#define VSPI_MISO MISO
#define VSPI_MOSI MOSI
#define VSPI_SCLK SCK
#define VSPI_SS   SS

#define HSPI_MISO 12
#define HSPI_MOSI 13
#define HSPI_SCLK 14
#define HSPI_SS   15
#endif

#if !defined(CONFIG_IDF_TARGET_ESP32)
#define VSPI FSPI
#endif

static const int spiClk = 1000000;  // 1 MHz

//uninitialized pointers to SPI objects
SPIClass *vspi = NULL;
SPIClass *hspi = NULL;

void setup() {
  //initialize two instances of the SPIClass attached to VSPI and HSPI respectively
  vspi = new SPIClass(VSPI);
  hspi = new SPIClass(HSPI);

  //clock miso mosi ss

#ifndef ALTERNATE_PINS
  //initialize vspi with default pins
  //SCLK = 18, MISO = 19, MOSI = 23, SS = 5
  vspi->begin();
#else
  //alternatively route through GPIO pins of your choice
  vspi->begin(VSPI_SCLK, VSPI_MISO, VSPI_MOSI, VSPI_SS);  //SCLK, MISO, MOSI, SS
#endif

#ifndef ALTERNATE_PINS
  //initialize hspi with default pins
  //SCLK = 14, MISO = 12, MOSI = 13, SS = 15
  hspi->begin();
#else
  //alternatively route through GPIO pins
  hspi->begin(HSPI_SCLK, HSPI_MISO, HSPI_MOSI, HSPI_SS);  //SCLK, MISO, MOSI, SS
#endif

  //set up slave select pins as outputs as the Arduino API
  //doesn't handle automatically pulling SS low
  pinMode(vspi->pinSS(), OUTPUT);  //VSPI SS
  pinMode(hspi->pinSS(), OUTPUT);  //HSPI SS
}

// the loop function runs over and over again until power down or reset
void loop() {
  //use the SPI buses
  spiCommand(vspi, 0b01010101);  // junk data to illustrate usage
  spiCommand(hspi, 0b11001100);
  delay(100);
}

void spiCommand(SPIClass *spi, byte data) {
  //use it as you would the regular arduino SPI API
  spi->beginTransaction(SPISettings(spiClk, MSBFIRST, SPI_MODE0));
  digitalWrite(spi->pinSS(), LOW);  //pull SS slow to prep other end for transfer
  spi->transfer(data);
  digitalWrite(spi->pinSS(), HIGH);  //pull ss high to signify end of data transfer
  spi->endTransaction();
}

Là où je me mêle les pinceaux, c'est dans le démarrage de mes SPI.
Voici mon code:

#include "FS.h"         // //FS comme dans « File System » : système d'enregistrement de fichiers Pour le lecter microSD
#include "SD.h"         // Pour le lecter microSD  ----- https://randomnerdtutorials.com/esp32-microsd-card-arduino/
#include "SPI.h"        // Pour le lecter microSD
#include <LedControl.h>
#include "chiffres_et_symboles.h"

// Définition des broches de raccordement Arduino Nano → Matrice LED pilotée par MAX7219  et carte mémoire microSD
#define broche_CLK          18 //GPIO-18  ... SPI microSD / broche no 30 -- vert       // noir => GND   blanc => 3.3V
#define broche_OUT          19 //GPIO-19  ... SPI microSD / broche no 31 -- gris
#define broche_DIN          23 //GPIO-23  ... SPI microSD / broche no 37 -- violet
#define broche_CS           5  //GPIO-05  ... SPI microSD / broche no 29 -- bleu
#define broch2_CLK          14 //GPIO-14  ... SPI écran / broche no 12  -- vert
#define broch2_OUT          12 //GPIO-12  ... SPI écran / broche no 13  -- non-connecté
#define broch2_DIN          13 //GPIO-13  ... SPI écran / broche no 15  -- blanc
#define broch2_CS           15 //GPIO-15  ... SPI écran / broche no 23  -- rouge
#define nbMatrices           5 //Combien y a-t-il d'écrans MAX72xx ?

LedControl matriceLed = LedControl(broch2_DIN, broch2_CLK, broch2_CS , nbMatrices);

void setup(void) {
  Serial.begin(115200);
  delay(400);

  //Gestionnaire de fichiers sur carte microSD externe
  SPI.begin(broche_CLK, broche_OUT, broche_DIN, broche_CS);
  SPI.begin(broch2_CLK, broch2_OUT, broch2_DIN, broch2_CS);

  if(!SD.begin(broche_CS)){
    Serial.println("Lecteur de carte microSD inaccessible ");
    carteSD = false;
  } else {
    uint8_t cardType = SD.cardType();
    if(cardType == CARD_NONE){
      Serial.println("Aucune carte microSD trouvée dans le lecteur idoine.");
      carteSD = false;
    }
    Serial.print("Type de carte trouvée: ");
    if(cardType == CARD_MMC){       Serial.println("MMC");
    } else if(cardType == CARD_SD){ Serial.println("SDSC");
    } else if(cardType == CARD_SDHC){ Serial.println("SDHC");
    } else { Serial.println("Inconnu");
    }
    uint64_t cardSize = SD.cardSize() / (1024 * 1024);
    Serial.printf("Taille de la carte microSD: %lluMB\n", cardSize);
    carteSD = true;
  }

  //Définitions utiles à l'affichage DEL 8x8
  for (int x=0; x<nbMatrices; x++) {
    matriceLed.setIntensity(x,8);
    matriceLed.clearDisplay(x);
    matriceLed.shutdown(x,true);
  }


}

J'ai ajouté la ligne

  SPI.begin(broch2_CLK, broch2_OUT, broch2_DIN, broch2_CS);

tout juste avant de vous écrire sans avoir testé.
Sans cette ligne, la matrice DEL (MAX7912) affiche n'importe quelle mélange DEL allumées. Des fois, ça ressemble vaguement à la valeur souhaitée ... un peu comme voir un visage dans un nuage, on peut reconnaître un « 2 » édenté, par exemple.
Oui, j'ai donc une réaction, mais je n'ai pas le résultat attendu.

Quelle pourrait être une bonne piste à explorer ?

Merci

Bien que SPI apparaisse dans le code, la librairie LedControl n'utilise pas de périphérique SPI. Elle utilise shiftOut() pour transférer les données vers l'afficheur.

Extrait de LedControl.cpp

void LedControl::spiTransfer(int addr, volatile byte opcode, volatile byte data) {
    //Create an array with the data to shift out
    int offset=addr*2;
    int maxbytes=maxDevices*2;

    for(int i=0;i<maxbytes;i++)
        spidata[i]=(byte)0;
    //put our device data into the array
    spidata[offset+1]=opcode;
    spidata[offset]=data;
    //enable the line 
    digitalWrite(SPI_CS,LOW);
    //Now shift out the data 
    for(int i=maxbytes;i>0;i--)
        shiftOut(SPI_MOSI,SPI_CLK,MSBFIRST,spidata[i-1]);
    //latch the data onto the display
    digitalWrite(SPI_CS,HIGH);
}  

Intéressant! Est-ce donc dire que je pourrais utiliser n'importe quel GPIO pour gérer mon affichage ? Si oui, qu'en est-il de la broche CLK ?

Si non, ce sera plus simple de connecter mes deux modules sur le SPI par défaut et gérer simplement le CS manuellement.

Toujours selon la même source - Ruis Santos - on passe à LOW le module avec lequel on entre en communication:

To select the peripheral you want to communicate with, you should set its CS pin to LOW. For example, imagine you have peripheral 1 and peripheral 2. To read from peripheral 1, make sure its CS pin is set to LOW (here represented as CS_1):

Question ....
Est-ce que les broches CS sont obligatoirement celles prédéfinies (dans le cas de ESP32, ce serait seulement 5 et 15) ? Ce me semble incompréhensible. Il me semble que je doive plutôt choisir des GPIO à mon goût, mais je ne vois nulle part qu'on utilise un GPIO en guise de SPI_CS. Si nous pouvons faire cela (utiliser n'importe quel GPIO), pourquoi TOUS les codes n'utiilsent QUE la broche indiquée SPI_CS ?

L'exemple donnée par Ruis Santos, se limite à 2 modules SPI. Si je suis autorisé à utiliser n'importe quel GPIO pour gérer l'activation d'un module plutôt qu'un autre, c'est donc dire que je peux gérer un nombre de modules SPI défini par le nombre de GPIO disponibles.

Pour commencer, je vais tester l'exemple que je donne ci-haut.

Je craignais que ma seconde ligne

SPI.begin(broch2_CLK, broch2_OUT, broch2_DIN, broch2_CS);

écrase la définition de sa précédente

SPI.begin(broche_CLK, broche_OUT, broche_DIN, broche_CS);

Je testerai donc

SPI.begin(broche_CLK, broche_OUT, broche_DIN, broche_CS);
SPI.begin(broch2_CLK, broch2_OUT, broch2_DIN, broch2_CS);

et vous reviendrai.

Merci

shiftOut utilise n'importe quelle broche car le transfert se fait avec des digitalWrite()


La libraire gère le transfert donc elle fera toujours des shiftOut().


Les questions suivantes sont hors de propos puisque la librairie LedControl n'utilise pas le SPI.


Ça ne peut pas fonctionner puisque les 2 begin() concernent la même instance de la classe SPI.
Dans l'exemple que tu donnes au début on crée 2 instances de la classe et on travaille en suite avec ces 2 instances comme expliqué dans les commentaires du code.

//uninitialized pointers to SPI objects
SPIClass *vspi = NULL;
SPIClass *hspi = NULL;

Je n'ai toujours pas compris pourquoi il fallait ajouter un deuxième SPI.
Le SPI gère plusieurs esclaves, il faut juste un gpio CS par esclave.

Il n'y a pas besoin d'un second SPI puisque de toutes les façons, il n'y a qu'un seul périphérique SPI, une carte SD si j'en crois le code. La librairie qui gère l'afficheur utilise shiftOut() en interne.

Il reste tout de même que mon affichage n'est pas conforme aux attentes. Le code soumis (sans la ligne ajoutéeSPI.begin(broch2_CLK, broch2_OUT, broch2_DIN, broch2_CS); ) m'affiche des pixels quasi-aléatoires tandis que le même panneau connecté au SPI et avec le code idoine m'affichait bien mon texte (même panneau DEL, même ESP32-Wrooom) seules ont changé les broches affectées au panneau DEL (et le code correspondant).

J'ai un peu de temps cet PM, je vais tenter de connecter sur SPI avec un CS distinct pour me deux modules ( lecteur SD et panneau DEL).

Je n'ai pas regardé le code, je fais confiance à @fdufnews.

Si le code utilise la fonction shiftout c'est que ton affichage n'a rien à voir avec le SPI.

Shiftout est fait pour remplir un registre à décalage.
Il y a trois fils :

  • un fil de données
  • un fil d'horloge
  • un fil "latch" ou verrou.

Un registre à décalage est constitué de bascule D chainées.
Les données sont appliquées sur la première bascule du registre.
A chaque coup d'horloge les données d'une bascule sont transférées sur la suivante.
Quand le registre fait 8 bits, c'est-à-dire 8 bascules en série, il faut 8 coups d'horloge pour remplir le registre.

Le latch sert à bloquer l'affichage le temps que le registre soit entièrement chargé, cela évite un scintillement désagréable.

Il y a bien des noms comme Data, Clock mais ce n'est que du vocabulaire, cela n'a rien à voir avec le SPI.

Le SPI est constitué de trois lignes qui sont communes à tous les esclaves.

  • MOSI = master out Slave in → le maître envoi un message à l'esclave.
  • MISO = master in Slave out → l'esclave répond au maître.
  • SCK = horloge qui fixe la vitesse de transmission

ET

  • une ligne appelée soit "CS" soit "SS" par esclave

Dans la norme du SPI un esclave sait que c'est à lui que le maître s'adresse si son CS est porté au potentiel de la masse.

Si tu as plus d'un esclave, n'importe quel gpio peut assurer la fonction CS de n'importe quel esclave.
Le CS de l'esclave n'est pas défini à l'initialisation du SPI, mais à l'initialisation de l'esclave.

Si tu veux utiliser d'autres pins pour le SPI que celles qui sont affectées par défaut dans le fichier pins_arduino.h de l'IDE il faut AVANT d'initialiser le lecteur SD ajouter cette ligne dans le setup() :
SPI.begin(PIN_SCK, PIN_MISO, PIN_MOSI, PIN_CS);

Bien évidement tu auras préalablement défini les pins :
#define PIN_SCK "numero que tu as choisis"

Note :
Si tu veux connaître, ou vérifier, les pins utilisées par défaut sur ta carte sans plonger dans l'IDE pour trouver le fichier pins_arduino.h, il suffit de faire un petit programme avec :

Serial.print("sck = ") ;
Serial.println(SCK) ;

idem pour MISO et MOSI et CS

Bonjour ,

  1. SPI et registres à décalage c’est la même chose

  2. il y a 2 périphériques SPI dans un ESP32 , ils peuvent être utilisés simultanément

  3. grâce à la matrice , ces 2 périphériques peuvent être raccordés à ‘(presque) n’importe quelle broche

a) c’est bien + élégant d’utiliser le SPI au lieu du médiocre shiftout

b) s’il y plusieurs esclaves , on peut utiliser un même SPI avec des CS différents , ou pour des raisons électriques , utiliser un même SPI maître sur des broches différentes , en jouant avec la matrice

Quel code? Celui que tu donnes au début utilise la librairie LedControl et celle-ci ne fait pas appel au SPI, ou alors il y a une librairie homonyme et qui fonctionne différemment..