eCUBE 8x8x8 1/2 (en projet)

Réalisation d'un cube 512 LEDs (8x8x8) pilotable par page WEB

PCB.png
PCB.png, janv. 2018

Cette 1ère partie traite de la réalisation du module de base 8x8 LEDs

CONCEPT

Les tutos de réalisation d'un cube de 8x8x8 LEDs ne manquent pas sur Internet.
Je leur trouve 3 inconvénients :
  1/ le câblage est lourd et complexe (souvent à cause de l'utilisation de registres à décalage)
  2/ ils ne sont pas accessibles par une page Web de contrôle
  3/ les animations sont figées dans le programme

L'idée est donc d'utiliser un ATmega 328 pour un module de 8X8 LEDs (16 broches), de multiplier le module par 8 et de contrôler chaque module par un Raspberry Pi, qui offrira le serveur HTTP/PHP/JS nécessaire au pilotage WEB ou par shell BASH/Python.
La quantité de fils va être drastiquement réduit. Les animations pourront être chargées via le Raspberry Pi.

" Un A[rdui]no pour contrôler les Hommes, les Nains, les Elfes, ..., Un pour les contrôler TOUS"

EQUIPEMENTS

Pour commencer, tous mes documents sont sur mon cloud ici.
Tous les documents sont libres de droit, modifiables et publiables, merci de simplement m'avertir si vous apportez des améliorations.
En matériel il vous faudra :
Un PC (très basique) sous Linux de préférence, Un Raspbberry Pi, 2 Arduino, dont un avec ATmega extractible comme celui-ci. Ce même Arduino servira, ATmega retiré, à téléverser le code généré par l'IDE Arduino. Le deuxième Arduino servira uniquement à la phase de téléchargement du "bootloader" sur les ATmega.

RÊVE !

Je ne vous cache pas que je serai comblé, si ce projet suscitait de l'intérêt par quelques uns d'entre vous, pour un développement collaboratif. En effet la phase suivante qui consistera à créer le eCube 8x8x8, si elle présente pas de difficulté particulière pour la partie électronique, me semble par contre ardue pour ce qui concerne la réalisation d'une page WEB interactive.

Les bonnes volontés sont les (TRÈS !) bienvenues.

MATÉRIEL ARDUINO

MODULE 8x8

La grille de 64 LEDs est un montage classique qui met en commun 8 anodes sur 8 lignes et 8 cathodes sur 8 colonnes. Des 2N2222 pilotent les cathodes pour ne pas charger les broches de l'Arduino, chaque colonne pouvant recevoir le courant cumulé de 8 anodes soit : 8x20mA.

Un problème est apparu avec certaines LEDs qui présentent une résistance relativement faible en inverse et font s'allumer toute la ligne de LEDs adjacentes. La solution a été de mettre une résistance de "PULLUP" sur chaque colonne de cathodes communes (v. schéma).
maquette de la matrice 8x8
IMG_20171119_111145.jpg, déc. 2017

J'utilise du fil rigide (cordes à piano 1mm) pour relier les LEDs. Elles sont fixées au moment de la soudure sur une matrice imprimée. (ajouter fichier STL)
Si vous ne possédez pas d'imprimante 3D, vous pouvez percer un panneau de bois aggloméré de 20x20cm avec une mèche de 5mm. Le pas entre chaque trou est un multiple de 2,54mm. J'ai choisi 7 x 2,54mm=17,78mm ce qui donnera un cube de ~142mm de côté.
Un fil fin relie chaque ligne à la carte du circuit.

Vous pouvez réaliser le circuit sur une plaque d'essai, ce qui demande un certain travail, ou vous pouvez aussi profiter du PCB dessiné à l'occasion sur Fritzing et dont je fournis et le fichier et la possibilité d'en demander la réalisation chez Aisler pour ~12€/carte (je ne suis pas payé pour ça, mais je trouve le procédé facile et professionnel).
https://aisler.net/fritzing
Cube8x8x8_1.fzz document Fritzing
Liste_des_composants.pdf

Cube8x8x8-ter_circuit_imprime.png
Cube8x8x8-ter_circuit_imprime.png, fév. 2018
 
 

Chaque carte reçoit un ATmega 328 chargé d'un "bootloader" pour simplifié le chargement du code écrit avec l'IDE Arduino. Voir le tuto pour charger un bootloader dans un ATmega qui n'en dispose pas.
Pour fonctionner les ATmega 328 ne nécessitent qu'une alimentation 5V et une horloge constituée d'un quartz et de 2 capacités de 22pF, j'ai ajouté une résistance d'1MH pour améliorer le fonctionnement.

ATMega 328P + bootloader + quartz = ARDUINO UNO !
Les modules sont programmables directement avec l'IDE Arduino.

Un des modules reçoit le régulateur de tension pour une alimentation entre 5V et 12V.
Les autres modules sont découpés pour laisser la place à un Raspberry Pi dans le socle du cube.

schema_electronique.png
schema_electronique.png, janv. 2018

Un connecteur [reset, Rx, Tx, GND, 5V] permet la programmation de l'ATmega. J'utilise pour ça un Arduino Uno dont l'ATmega est retiré, cela me permet de récupérer l'électronique d'adaptation USB/UART à moindre frais. Une autre solution consiste à se procurer une carte USB/UART spécifique. Cette connexion n'est utile que pour la période d'écriture / débogage du programme de l'Arduino.
Pour connecter le Raspberry Pi, j'utilise une connexion SSH qui se fait via le réseau WIFI.

LOGICIEL ARDUINO

ORGANISATION DE LA GRILLE[0]

L'affichage des LEDs est représenté en mémoire par un tableau de 8x8 octets (GRILLE). Un octet représente une ligne de 8 LEDs. Le bit de poids le +faible correspond à La LED la + à gauche. L'octet de la GRILLE[x][0] représente la ligne la +basse. L'origine de notre grille est donc [0,0] en bas à gauche, la LED en haut à droite est codée [7,7].

MÉMOIRES INTERNES AUX ARDUINO

Les Arduino disposent chacun de 3 zones de mémoires :
     1/ Pour l'affichage des LEDs une grille[0] de 8x8 octets est nécessaire, on vient de le voir, celle-ci est accompagnée de 15 autres grilles[1 à15] qui seront affichées en fonction d'un paramètre de sélection de grille.
Il sera possible de charger chacune des 15 grilles et d'en afficher le contenu à la demande.
    2/ Une zone de mémoire de données de 15x32+8 octets, qui correspond à 15 cellules de 32 octets + 8 octets de mémorisation de paramètres des instructions. Ceci sera détaillé +loin.
    3/ Une zone tampon de stockage des instructions reçues. En effet comme le Raspi et les Arduino fonctionnent en mode désynchronisé, il est nécessaire de laisser fonctionner chacun à son rythme. Le tampon fonctionne en mode FIFO (First In, First Out) sur 128x3 octets.

COMMUNICATION INTER-MODULES ET RASPI

J'ai choisi d'utiliser le bus I2C pour communiquer en sens unique depuis le Raspi vers l'Arduino.
Le bus est simple à utiliser, ne nécessite que 2 fils et permet d'adresser plusieurs dizaines d'équipements.
Le Raspi est paramétré en maître et les Arduino en esclaves. Chaque environnement dispose de bibliothèque facilitant la programmation.
Chaque module 8x8 va donc recevoir une adresse différente que le Raspi utilisera pour dialoguer individuellement avec ceux-ci.

ADDRESSAGE

Afin d'éviter que le programme des Arduino ne soit différent pour chaque module, un adressage physique est mis en place et une lecture de cette adresse est faite après chaque reset. J'utilise pour cela, 3 des 8 broches pilotant les cathodes de la grille de LEDs. La lecture de l'état de ces 3 broches ne doit pas déranger l'utilisation par la suite des broches en sortie. Je positionne donc le temps de la lecture, les 3 broches en INPUT_PULLUP (pour mettre une résistance de ~10k au VCC), une autre résistance de 3,3K est connectée ou non au GND. J'obtiens un 0 ou un 1 en fonction de la présence ou non de la résistance.
L'adressage démarre à 0x8 et peut atteindre 0xF si les 3 broches sont à 1, chaque broche ayant un poids = 2^n.

organigramme.png
organigramme.png, mar. 2018

Le code de lecture est simple :

const byte   CATHODE[8] = {17,15,11,9,5,1,6,0}; // broches utilisées pour les CATHODEs
const byte OffsetAdd = 2; 
byte I2Caddress=0x8;
void setup() {
  // la présence des straps ajoutent les poids : 4 + 2 + 1
  for (byte i=0; i<3; i++) {
    pinMode(CATHODE[i+OffsetAdd], INPUT_PULLUP); // 3 broches des CATHODES sont utilisées en INPUT juste le temps de lire l'@ du module
    I2Caddress = I2Caddress + (digitalRead(CATHODE[i+OffsetAdd]) << i);  // n décalage.s à gauche = 1*2^n
  }

UTILISATION DES BIBLIOTHÈQUES

Du côté Raspi, on va utiliser Python et la bibliothèque "smbus" :

#!/usr/bin/python
# -*- coding: utf-8 -*-
import smbus                               # utilisation de la bibliothèque SMBUS
bus = smbus.SMBus(1)              # initialisation du bus I2C
bus.write_byte(add, data)         # envoi d'un octet "data" au module portant l'adresse "add"

Côté Arduino :

#include <Wire.h>                       // déclaration de la bibliothèque
  // bus I2C
  Wire.begin(I2Caddress);              // initialise le bus I2C en esclave
  Wire.onReceive(receiveEvent);   // initialisation en mode interruption
// FONCTION RECEIVE - exécutée à chaque réception de données depuis le maître en mode interruption
void receiveEvent(int howMany) {
  while (1 < Wire.available()) {  } // attente de la donnée   
  byte intRecu = Wire.read();         // reçoit 1 octet   
  DONNEES = intRecu;                    // affectation du byte reçu
}

SÉLECTION DES DONNÉES / INSTRUCTIONS

Il est pratique de pouvoir différencier le type d'information envoyé aux Arduino, je vais utiliser la broche libre 2 (INT 0) pour ça :  0 (LOW) ce sont des données - 1(HIGH) ce sont des instructions
Rappelons à ce stade, que tant que le Raspi est en OUTPUT, il envoie un 3V3 sur l'entrée de l'Arduino, ce qui suffit à définir un état 1(HIGH). Il ne faudrait pas utiliser le Raspi en entrée qui recevrai un 5V destructeur. Un pont réducteur de tension serait alors nécessaire.

DIALOGUE ENTRE MODULES ET RASPBERRY PI

Le Raspi va pouvoir envoyer aux Arduino des instructions codées sur 3 octets maximum. Ceci va permettre de réduire les temps de communication et surtout la quantité de mémoire utilisée de côté des Arduino.
     a/ le 1er octet contient sur :
           > 4 bits de poids fort, le niveau de grille à afficher
           > 4 bits de poids faible, le code d'instruction
     b/ les 2 autres octets codés eux aussi sur  2x4 bits, contiendront de 1 à 4 paramètres utiles en fonction du besoin de l'instruction.

3 méthodes sont mises en place pour permettre les animations du eCube.

  1. Des ordres directement envoyés par le Raspi sont exécutés par le module concerné. Ces ordres sont des primitives accompagnées de paramètres comme :
    - efface ()                  : efface le plateau
    - ligne (X,Y,T)            : affiche soit un point ou une ligne : hor., ver., en diag. ou une croix en +, en X ou *
    - carre (X,Y,X1,Y1)     : dessine un carré vide de l'angle X,Y à l'angle opposé en X1,Y1
    - CARRE (X,Y,X1,Y1)  : dessine un carré plein de l'angle X,Y à l'angle opposé en X1,Y1
    - cercle (D,X,Y)          : dessine un cercle de diamètre D, centré en X,Y
    - delai (D)                  : défini le rythme de l'animation
    - (X) select                 : permet de sélectionner la grille à afficher en plus de la grille 0
    - car (C)                     : affiche un caractère de 0 à 9 et de A à Z
    - charge (fichhier)     : charge une animation en mémoire à partir du "fichier"
    - joue (D,F,T)             : joue une animation chargée en mémoire de D à F, de type T (v. +loin)
    - reset ()                     : remise à zéro des modules Arduino
  2. Des animations constituées de motifs ou de suites d'instructions, peuvent être chargées dans une mémoire de 15 cellules de 32 octets. Chacune de ces cellules pouvant stocker 4 motifs de grille (4x8 octets) ou 10 instructions (10x3 (+2 octets perdus)). Ces cellules seront "jouées" avec l'instruction "joue" et les paramètres [cellule de Début] [cellule de Fin] et [Type de cellule].(v. les commandes associées ci-dessus). Le type de cellule peut être "M" pour une suite de motifs, "I" pour une suite d'instructions et "G" pour un jeu de grille (v. ci après).
  3. Après un pré-remplissage des grilles[1 à 15], la sélection de la grille à l'affichage permet une animation sur 15 motifs différents (la grille[0] est toujours affichée).

programme à charger sur l'Arduino : Module8x8.ino

films de démonstration : Images diverses du projet :

LE RASPBERRY PI

J'ai choisi de faire le développement du projet sur un Raspberry Pi 0W.
Celui-ci est installé sous Raspian lite qui est beaucoup + économe en ressources (Y'a pas d'écran graphique. Et ça suffit pour ce projet !).
On va passer en mode SU :
  root@picube:/home/pi#  sudo su
À l'aide du :
  root@picube:/home/pi# raspi-config
il faut activer le bus I2C, le WIFI et l'accés SSH
J'y ai installé un serveur Apache + PHP, la bibliothèque WiringPi ainsi que les I2c-Tools et le WiringPi Python-smbus:
  root@picube:/home/pi# apt install apache2 php5 i2c-tools wiringpi python-smbus
À ça, j'ajoute pour mon confort personnel : Rsync, Samba, joe, htop et ntp
   root@picube:/home/pi# apt install samba rsync joe htop ntp
NTP est un client d'accès aux serveurs de temps... Le Raspi est toujours à l'heure
SAMBA va permettre un partage des dossiers pour les PC sous MS Windows.
Je suis sympa, je vous donne une configuration de partage pour Samba :
   root@picube:/home/pi# nano /etc/samba/smbd.conf
       ajoutez ces lignes à la fin du fichier :
       [PI]
             path = /home/pi
             browseable = yes
             read only = no
             force user = pi
             force group = pi
             create mode = 0774
             directory mode = 0774
  
root@picube:/home/pi# smbpasswd -a pi
        donnez un mdp à l'utilisateur samba "pi"
   root@picube:/home/pi#
   root@picube:/home/pi# service smbd restart

Sur votre PC MS Windows, vous pouvez connecter le service par : \\VOTRE_PI\PI (de préférence dans la cuvette, S.V.P. ! ;) ).
Si vous êtes sous Windows 10 et que ça ne fonctionne pas... Démerdez-vous ! je ne suis pas là pour vous aider à utiliser un système de M.... ! Passez sous Linux !
Pour ce qui me concerne, comme je n'utilise QUE du LIBRE, mon PC est sous Linux XUBUNTU et je connecte le dossier du PI par SFTP (v. Gigolo). De cette manière je travaille sur mon PC avec BlueFish comme éditeur de programmes Python et shell Bash. J'utilise un terminal SSH pour lancer et tester mes programmes

Revenons à nos moutons...
Le Raspi est normalement prêt.

Le programme le plus important est celui qui va transmettre les instructions aux Arduino. Ilest écrit en Python, Je l'appelle "ecube.py". Pour simplifier sont utilisation, je créai un "alias" par la commande :
   root@picube:/home/pi# alias e3="python /home/pi/ecube.py"
cette commande peut être insérée dans le fichier /home/pi/.profile pour la retrouver à chaque connexion.

MODE COMMANDE

Des ordres directement envoyés par le Raspi sont exécutés par le module concerné. Ces ordres sont des primitives accompagnées de paramètres, exemples :

  1. format des commandes :     e3 N inst [[[[P1] P2] P3] P4]

        suppose "e3"="python /home/pi/ecube.py", N: n° de grille, P(1,2,3,4): paramètres dépendants de la fonction. il est obligatoire de passer "N", même si non utilisé.

        Instructions :

     e3 x reset : remise à zéro de l'ATmega (x quelconque)

     e3 N efface : efface la GRILLE n° 'N' | 'N'=0..15

     e3 N select : sélectionne la GRILLE n° 'N'et la met à l'affichage

     e3 x delai D : change la valeur de tempo | ‘D’=0..15

     e3 x charge 'fichier' : transfert contenu de 'fichier' dans DONNEES de l'ATmega

     e3 N transf C P  : transfert le contenu de la CELLULE 'C', position 'P' sur la GRILLE 'N' | 'C'=0..15, 'P'=0..3 (note: si P>3 on déborde sur la CELLULE C+n, ce qui n'est pas un inconvénient)

     e3 x joueP1 P2 type : joue une série de motifs ou d’instructions | Pn=0..15

         type = grille : sélectionne les GRILLES de ‘P1’ à ‘P2’

         type = motif : affiche les motifs des CELLULES ‘P1’ à ‘P2’

         type = inst : exécute les instructions des CELLULES P1 à P2

         type = cache : ré-exécute les ‘P1’ instructions arrières du cache FIFO.Pour ‘P2’=0 joue en boucle sinon 1 fois (la boucle s’arrête à la 1ère instruction reçue)

     e3 N car C: dessine une caractère alphanumérique ‘C’ (0..9, A..Z)sur la GRILLE ‘N’

     e3 N ligne X Y type dessine une ligne (point, lignes, croix, etc.) passant par ‘X’‘Y’ sur la GRILLE ‘N’ | X & Y=0..15

        type = .  : dessine un point

        type = -  : dessine une ligne horizontale

        type = ‘|’ : dessine une ligne verticale (utilisation de «» ou ‘ ’ ou \ obligatoire)

        type = G  : dessine une diagonale orientée à gauche (le symbole ‘\’ n’est pas utilisable, grrr !)

        type = /  : dessine une diagonale orientée à droite

        type = +  : dessine une croix

        type = X  : dessine une croix en X

        type = ‘*’ : dessine une étoile (utilisation de «» ou ‘’ ou \ obligatoire)

     e3 N carre X Y X1 Y1 : dessine un carré vide depuis ‘X’‘Y’ à ‘X1’’Y1’ sur la GRILLE ‘N’

     e3 N CARRE X Y X1 Y1 : dessine un carré plein depuis ‘X’‘Y’ à ‘X1’’Y1’ sur la GRILLE ‘N’

     e3 N cercle D X Y : dessine un cercle centré en ‘X’‘Y’sur la GRILLE ‘N’

  2. Des animations constituées de motifs ou de suites d'instructions, peuvent être chargées dans une mémoire de 15 cellules de 32 octets. Chacune de ces cellules pouvant stocker 4 motifs de grille (4x8 octets) ou 10 instructions (10x3 (+2 octets perdus)). Ces cellules seront "jouées" avec l'instruction "joue" et les paramètres [cellule de Début] [cellule de Fin] et [Type de cellule].(v. les commandes associées ci-dessus). Le type de cellule peut être "motif" pour une suite de motifs, "inst" pour une suite d'instructions et "grille" pour un jeu de grille (v. ci après). Le code "cache" rejoue les 'P1' instructions stockées dans le FIFO
     
  3. Après un pré-remplissage des grilles[1 à 15], grâce à la commande 'transf' la sélection de la grille à l'affichage permet une animation sur 15 motifs différents (la grille[0] est toujours affichée).
     

EXEMPLE 

Voici un exemple de script possible en Shell Bash pour créer une animation :

   root@picube:/home/pi#  nano animation.sh

  • #!/bin/bash
  • e3="python /home/pi/ecube.py"   # pour simplifier l'usage du programme Python ecube.py
  • $e3 0 retorse                                      # r. à z. du module
  • $e3 0 charge mes_motifs.txt          # chargement du fichier de motifs dans DONNEES[488]
  • for (( i=0; i<15; i++ )) ; do $e3 $[ $i+1 ] transf 0 $i ; done 
    # boucle de transfert des motifs de DONNEES[488] vers les GRILLE[x]
  • $e3 0 joue 1 15 grille                      # lancement 1ère animation 
  • $e3 0 joue 1 0 cache                       # lancement animation en boucle
  • sleep 10                                           # l'animation se joue 10'
  • $e3 0 select                                     # stoppe l'animation et efface la grille en cours
  • exit

   root@picube:/home/pi#  chmod + x animation.sh   # à ne faire qu'une seule fois
   root@picube:/home/pi#  ./animation.sh

MOTIFS

Les motifs sont une succession de 8 octets, le 1er représente la 1ère colonne, etc...
Si j'inscris "255,0,0,0,0,0,0,0" dans un fichier et que je charge le motif, j'obtiendrai une ligne verticale sur la 1ère colonne.
Pour simplifier la création de ces fichiers de motifs, je mets à votre disposition un document "calc" (Libre Office) motifs.ods qui permet de dessiner les grilles et de récupérer les codes correspondants.
Il suffit alors de sélectionner et de copier l'entête de chaque série de 4 cellules puis de la coller dans un fichier de texte avec un éditeur simple (Kate, Wordpad, Notepad, etc...)
Chaque ligne de ce fichier comprend donc 32 caractères et correspond à une cellule.
Il ne faut pas dépasser 15 lignes, soit 15 cellules (sinon gare au débordement de variable que l'IDE Arduino ne gère pas).
 

INSTRUCTIONS

C'est un peu plus compliqué pour les instructions, car il faut les donner au format compressé sous 3 octets, donc 6x4bits.

Je vous redonne le format ici : [ inst , level ][ P2 , P1 ][ P4 , P3 ]
les codes instruction envoyés aux Arduino sont :

  • 0  : reset
  • 1  : efface
  • 2  : ligne
  • 3  : carre
  • 4  : CARRE
  • 5  : cercle
  • 6  : car
  • 7  : non utilisé (éventuellement libre pour une commande de dessin de triangle)
  • 8  : non utilisé
  • 9  : delai
  • 10: select
  • 11: joue
  • 12: non utilisé
  • 13: non utilisé
  • 14: transf
  • 15: charge

Pour nous aider à déterminer les bonnes succession de codes à inscrire dans un fichier texte, je vous donne une version modifiée du programme "ecube.py"  nommé ecube-v.py ("v" comme verbose) qui affiche dans le terminal le code envoyé aux Arduino.

Ce qui donne par exemple :
  root@picube:/home/pi#  alias e3v="python /home/pi/ecube-v.py"  # à ne faire qu'une fois !
  root@picube:/home/pi#  e3v 0 joue 1 11 grille
    11
    177
    0

Il ne vous suffit plus qu'à inscrire dans le fichier, chaque valeur séparée par une ",".