Pilotage de registres à décalage 74HC595

publication: 15 janvier 2024 / mis à jour 15 janvier 2024

Read this page in english

 


Appel à collaboration

Vous développez des montages, simples ou complexes avec ESP32 et ESP32forth.

Partagez-les ici sur ce site.

ESP32forth ne pourra se développer qu'avec la collaboration active de toutes les bonnes volontés.

Vos montages peuvent aider d'autres développeurs.

Les montages des autres développeurs peuvent vous aider.

Pour proposer un article ou un montage, cliquez ici


Préambule

Début janvier 2024, j'ai commandé et reçu deux cartes ESP32-C3-Zero. Ce sont des cartes ESP32 mono coeur, avec un processeur RISC-V. C'est une vraie carte ESP32, mais qui fait la moitié de la longueur d'une carte ARDUINO Nano:

à gauche: ARDUINO NANO
à droite: la carte ESP32-C3-Zero

Contrairement à la carte ARDUINO NANO, cette carte ESP32-C3-Zero dispose des interfaces WiFi, Bluetooth, SPI, I2S, UART... Et aussi la carte ESP32-C3-Zero utilise un processeur cadencé à 160 Mhz, quand la carte ARDUINO NANO est cadencée seulement à 16 Mhz.

L'autre bonne surprise, c'est l'installation de ESP32forth. Elle s'est déroulée sans souci. Mais surtout, c'est la place disponible dans le dictionnaire FORTH:

--> ESP32forth v7.0.7.17 - rev af26e61de914602aa8f4193ce34d728cd0f209fa
    ESP32-C3   160 MHz   1 cores   4194304 bytes flash
         System Heap: 59840 free + 281920 used = 341760 total (17% free)
                      44020 bytes max contiguous
    Forth dictionary: 152048 free + 33132 used = 185180 total (82% free)
    3 x Forth stacks: 2048 bytes each

Quand on connait l'extraordinaire compacité du langage FORTH, autant dire que ce sera très difficile d'exploiter toute cette place!

Seule bémol, c'est le nombre de ports GPIO disponibles, seulement 15 ports GPIO. C'est donc l'occasion, au travers de cet aricle de montrer que cette contrainte peut être contournée sans problème.

Le registre à décalage 74HC595

C'est un composant électronique monté dans un boitier DIN 16 broches. Disposition des pins:

Voici comment le composant 74HC595 traite les signaux transmis par un micro-contrôleur:

Nous n'avons besoin de gérer que trois entrées sur le registre à décalage 74HC595: DS, SHCP et STCP. Voyons comment connecter ces entrées à notre carte ESP32-C3-Zero. A noter que les explications qui suivent sont valables sur toutes les cartes ESP32.

Câblage des registres 74HC595

Le câblage des registres 74HC595 ne nécessite que trois fils en sortie de la carte ESP32.

Voici les connections à réaliser entre la carte ESP32 et le registre 74HC595:

GPIO 06 CS1  -- 12 STCP
GPIO 07 MOSI -- 14 DS
GPIO 08 SCLK -- 11 SHCP

Avec ce montage, on peut gérer 24 sorties supplémentaires. Il n'y a pas de limite au nombre de registres 74HC595 pouvant être chaînés.

Paramétrer le port SPI de la carte ESP32

Sur la carte ESP32-C3-Zero, tous les pins GPIO 00 à 10 sont utilisables pour une sortie SPI. En pré-requis, vous devez implanter la librairie SPI. Tout est expliqué ici:
Implémentation du vocabulaire SPI

J'ai été surpris, lors de mes recherches, avec ces mots clés "ESP32 SPI 74HC595", de voir que sur de très nombreux sites on affirme qu'on ne peut pas piloter le composant 74HC595 par un port SPI. Sur GITHUB, il existe quelques contributions démontrant le contraire, mais sans explication.

Seule une page web, utilisant micro-Python, explique les paramétrages de la carte SPI, mais pour ARDUINO!

J'ai procédé à quelques essais, en testant les signaux transmis, avec un oscilloscope d'entrée de gamme. Voici les paramètres du port SPI pour la carte ESP32-C3-Zero:

 6 constant CS1     \ Chip Select, SS1 SPI signal 
 7 constant MOSI    \ Master Out Slave In 
 8 constant SCLK    \ Signal CLocK 
-1 constant MISO    \ unused 
 
100000 constant SPI_FREQ 
 
0 constant LSBFIRST  
1 constant MSBFIRST  
 
\ definition of SPI_MODE0..SPI_MODE3 constants   
0 constant SPI_MODE0 
1 constant SPI_MODE1   
2 constant SPI_MODE2   
3 constant SPI_MODE3  
 
: SPI.init ( -- ) 
    CS1     OUTPUT pinMode          \ set pins mode 
    [ SPI ] 
    SCLK MISO MOSI CS1 SPI.begin    \ set SPI GPIOs 
    SPI_FREQ SPI.setFrequency       \ set SPi frequency transmission 
    MSBFIRST SPI.setBitOrder        \ set bit order 
    SPI_MODE0 SPI.setDataMode       \ set SPI mode 
    [ FORTH ] 
  ; 

Dans notre code source, cette ligne -1 constant MISO indique qu'on n'attribue aucun port GPIO au signal MISO (Master In Slave Out).

Dans la définition de SPI.init, seule la sortie CS1 est paramétrée en sortie: CS1 OUTPUT pinMode. Il n'est pas nécessaire de paramétrer les autres sorties SCLK, MOSI et CS1, c'est SPI.begin qui s'en charge.

Dans certains codes sources en langage C, la fonction SPI.begin est utilisée en dernier. C'est une erreur. Cette fonction doit être appelée avant de compléter le paramétrage du port SPI.

L'intérêt de passer par l'interface SPI est évident. Grâce à cet interface, il n'est plus nécessaire de gérer un signal d'horloge, sérialiser des octets. Si on respecte le standard SPI, voici le chronogramme des actions à réaliser pour transmettre un octet:

Sauf que ce chronogramme NE FONCTIONNE PAS!

Transmission des données par le port SPI

Voici d'abord le code FORTH pour créer et gérer un tampon de données destinées à nos registres 74HC595:

3 constant NB_SHIFT_REGS 
 
create DATAS 
    NB_SHIFT_REGS  allot 
 
\ store byte at pos in DATAS, pos [0..NB_SHIFT_REGS-1] 
: data!  ( byte pos -- ) 
    DATAS + c! 
  ; 

Le tampon DATAS est dimensionné en fonction du nombre de regsitres 74HC595. ici, il est dimensionné à trois octets.

Le mot data! permet de mettre une valeur à transmettre, exemple 64 0 data! enregistre la valeur 64 dans le premier octet de DATAS.

Voici le code pour transmettre le contenu du tampon DATAS:

: DATAS.send ( -- ) 
    [ SPI ] 
    NB_SHIFT_REGS 0 do 
        DATAS NB_SHIFT_REGS 1- i - + 
        c@ SPI.write 
    loop 
    1 SPI.setHwCs 
    1 ms 
    0 SPI.setHwCs 
    [ forth ] 
  ; 

Ici la boucle

do..loop

traite d'abord dle dernier octet dans DATAS et termine par le premier octet.

C'est la séquence c@ SPI.write qui transmet un octet vers les registres 74HC595.

Mais ce qui différence surtout cette définition par rapport au standard de transmission SPI, c'est l'utilisation du signal CS après la transmission en série des octets. En fin de transmission, on met le pin CS1 à l'état haut avec 1 SPI.setHwCs, puis on attend une milliseconde et on redescend au niveau 0 avec 0 SPI.setHwCs.

Sur cette photo, on a testé ce simple code de test:

SPI.init 
255 0 data! 
DATA.send 

Dans ce montage, seul un registre a décalage est câblé sur huit LEDs. L'ordre de transmssion des octets doit toujours s'effectuer en commençant par l'octet final. Si on numérote nos octets successivement c0 c1 et c2, l'ordre de transmission des octets sera c2 c1 et c0 selon notre boucle:

: DATAS.send ( -- )
    [ SPI ]
    NB_SHIFT_REGS 0 do
        DATAS NB_SHIFT_REGS 1- i - +
        c@ SPI.write
    loop
    1 SPI.setHwCs
    1 ms
    0 SPI.setHwCs
    [ forth ]
  ;

Il y a moyen de simplifier le code en bleu et rouge.

Transmission SPI sans boucle

C'est après une succession d'essais divers que le choix s'est arrêté sur le mot SPI.writeBytes. Ce mot accepte deux paramètres:

En langage C, la fonction writeBytes() de la librairie SPI est très mal documentée. Et les codes sources exploitant cette fonction sont indigents. C'est malheureusement le lot des librairies utilisées telles quelles. En fait, les programmeurs utilisent du code sans chercher à approfondir.

On utilisera SPI.writeBytes pour transmettre le contenu de DATAS sans utiliser de boucle de transmission:

DATAS 3 SPI.writeBytes 

Le souci, avec ce code, c'est que l'octet c0 sera transmis en premier, c1 en second, c2 en dernier. En transmettant les octets dans cet ordre, les registres à décalage vont traiter ces octets dans cet ordre:

La solution la plus simple consistera donc à inverser les valeurs à affecter aux octets de DATAS:

\ store byte for regn in DATAS, regn [0..NB_SHIFT_REGS-1] 
: data!  ( byte regn -- ) 
    NB_SHIFT_REGS 1- swap -      \ calculate inverse offset 
    DATAS + c!               
  ; 

Ici, le paramètre regn doit être le numéro de registre, 0 pour le premier registre, 1 pour le suivant, etc...

La définition DATAS.send réécrite:

: DATAS.send ( -- ) 
    DATAS NB_SHIFT_REGS SPI.writeBytes 
    nop 
    1 SPI.setHwCs 
    nop nop 
    0 SPI.setHwCs 
  ; 

Nous allons voir comment exploiter de manière pratique cette nouvelle définition.

Pilotage de huit LEDs connectées à un registre 74HC595

Voici le câblage de huit LEDs connectées à un registre 74HC595:

L'utilisation de LEDs n'a aucun intérêt pratique. Ici, ces LEDs servent à vérifier le bon fonctionnement du câblage et des définitions FORTH. En pratique, ce peut être des relais ou tout autre appareil pouvant être commandé par les sorties Q0..Q7 de nos registres à décalage 74HC595.

Voici une toute petite définition:

0 constant 1ST_74HC         
         
: xs ( n -- ) 
    1ST_74HC data! 
    DATAS.send ; 

Résultat des tests:

Toute autre valeur entre [1..255] matérialisera la valeur binaire utilisée par le mot xs.

Il est déconseillé d'allumer toutes les LEDs, celles-ci étant alimentées via la carte ESP32. Pour allumer toutes les LEDs, il est fortement recommandé d'alimenter les registres à décalage avec une alimentation indépendante.


Les codes sources complets sont accessibles ici:
https://github.com/MPETREMANN11/ESP32forth/tree/main/__my%20projects/GPIOs/74HC595


Legal: site web personnel sans commerce / personal site without seling