Construire un affichage POV haute résolution en utilisant ESP32

Build your own POV display

Comme mentionné précédemment, le PCB utilisé dans ce projet a été fabriqué par Viasion Technology, qui est un expert en fabrication de PCB et assemblage de PCB en Chine. Depuis 2007, ils ont fourni des PCB de haute qualité à plus de 1000 clients dans le monde entier. Leur usine est certifiée UL, ISO9001:2016 et ISO13485:2016 et est l’un des fabricants de PCB les plus fiables en Chine pour la production en petite et moyenne quantité.

Vous pouvez également télécharger le fichier Gerber pour le PCB d’affichage POV, et contacter Viasion Technology pour faire fabriquer vos PCB comme nous l’avons fait. Leur emballage et la livraison des cartes étaient très satisfaisants et vous pouvez également vérifier la qualité de leurs cartes PCB ci-dessous. 

Construire un affichage POV haute resolution en utilisant ESP32

POV Display Image to Code converter

Code

/*
 * Nom du projet : affichage POV 
 * Description du projet : Firmware pour l’affichage POV ESP32. Résolution de l’affichage : 128 pixels
 * Auteur : Jobit Joseph
 * Droits d’auteur : Jobit Joseph
 * Droits d’auteur : Semicon Media Pvt Ltd
 * Droits d’auteur : Circuitdigest.com
 * 
 * Ce programme est un logiciel libre : vous pouvez le redistribuer ou le modifier
 * selon les termes de la GNU General Public License telle que publiée par
 * Free Software Foundation, en version 3.
 *
 * Ce programme est distribué dans l’espoir qu’il sera utile,
 * mais SANS AUCUNE GARANTIE ; sans même la garantie tacite de
 * QUALITÉ MARCHANDE ou d’ADÉQUATION À UN USAGE PARTICULIER. Consultez les
 * conditions de la GNU General Public License pour plus de détails.
 *
 * Vous devriez avoir reçu une copie de la GNU General Public License
 * avec ce programme. Sinon, consultez <http://www.gnu.org/licenses/&gt;.
 *
 */
#include <Arduino.h>
#include <MultiShiftRegister.h>
#include « Images.h »
#include « Precompute.h »

// Définition des broches
#define latchPin 14
#define clockPin 15
#define dataPin 13
#define latchPinx 17
#define clockPinx 18
#define dataPinx 16
#define HALL_SENSOR1_PIN 36
#define HALL_SENSOR2_PIN 39

// Variables globales
volatile unsigned long lastHallTrigger = 0;
volatile float rotationTime = 0;  // Temps pour une rotation en millisecondes
int hallSensor1State = 0;
int hallSensor2State = 0;
bool halfFrame = false;
int numberOfRegisters = 8;
int offset = 270;
int repeatvalue = 1;
int hys = 3000;
int frame = 0;
int repeat = 0;
int anim = 0;
int frameHoldTime = 1;     // Nombre de boucles pour maintenir chaque frame
int frameHoldCounter = 0;  // Compteur pour suivre les boucles de la frame actuelle

// Instances des pilotes de registres à décalage pour les deux bras
MultiShiftRegister msr(numberOfRegisters, latchPin, clockPin, dataPin);
MultiShiftRegister msrx(numberOfRegisters, latchPinx, clockPinx, dataPinx);

// Fonction pour calculer les coordonnées polaires et obtenir les données correspondantes dans les tableaux.
int getValueFromAngle(const uint8_t arrayName[][16], int angle, int radius) {
  // Ajuster l’angle en soustrayant l’offset pour une rotation dans le sens inverse des aiguilles d’une montre
  int adjustedAngle = angle – offset;
  if (adjustedAngle < 0) adjustedAngle += 360;  // S’assurer que l’angle reste dans la plage de 0 à 359 degrés

  // Inverser le calcul de targetX pour retourner l’image horizontalement
  int targetX = 127 – precomputedCos[radius][adjustedAngle];  // Retournement de targetX
  int targetY = precomputedSin[radius][adjustedAngle];
  if (targetX >= 0 && targetX < 128 && targetY >= 0 && targetY < 128) {
    int byteIndex = targetX / 8;
    int bitIndex = 7 – (targetX % 8);
    return (arrayName[targetY][byteIndex] >> bitIndex) & 1;  // Extraire la valeur du bit et la renvoyer
  } else {
    return -1;  // Hors de la plage
  }
}

// Routine d’interruption pour le capteur Hall 1
void ISR_HallSensor1() {
  unsigned long currentTime = micros();
  // Vérifier si HYS ms se sont écoulées depuis la dernière détection
  if (currentTime – lastHallTrigger >= hys) {
    rotationTime = (currentTime – lastHallTrigger) / 1000.0;
    lastHallTrigger = currentTime;
    hallSensor1State = 1;
    halfFrame = true;
  }
}

// Routine d’interruption pour le capteur Hall 2
void ISR_HallSensor2() {
  unsigned long currentTime = micros();
  // Vérifier si HYS ms se sont écoulées depuis la dernière détection
  if (currentTime – lastHallTrigger >= hys) {
    lastHallTrigger = currentTime;
    hallSensor2State = 1;
    halfFrame = false;
  }
}

// Fonction pour calculer les RPM et afficher chaque frame en conséquence 
void DisplayFrame(const uint8_t ImageName[][16]) {
  float timePerSegment = rotationTime / 360.0;  // Supposons 180 segments par demi-rotation

  for (int i = 0; i < 180; i++) {
    unsigned long segmentStartTime = micros();

    for (int j = 0; j < 64; j++) {
      // Le premier bras (msr) affiche la première moitié de la frame
      if (getValueFromAngle(ImageName, i + (halfFrame ? 0 : 180), j)) {
        msr.set(j);
      } else {
        msr.clear(j);
      }

      // Le deuxième bras (msrx) affiche la deuxième moitié de la frame
      if (getValueFromAngle(ImageName, i + (halfFrame ? 180 : 0), j)) {
        msrx.set(j);
      } else {
        msrx.clear(j);
      }
    }

    msr.shift();
    msrx.shift();

    while (micros() – segmentStartTime < timePerSegment * 1000)
      ;
  }
}

void setup() {
  // Initialiser les broches
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  pinMode(latchPinx, OUTPUT);
  pinMode(clockPinx, OUTPUT);
  pinMode(dataPinx, OUTPUT);
  pinMode(HALL_SENSOR1_PIN, INPUT);
  pinMode(HALL_SENSOR2_PIN, INPUT);

  attachInterrupt(digitalPinToInterrupt(HALL_SENSOR1_PIN), ISR_HallSensor1, RISING);
  attachInterrupt(digitalPinToInterrupt(HALL_SENSOR2_PIN), ISR_HallSensor2, RISING);
  for (int i = 0; i < 64; i++) {
    msr.clear(i);
    msrx.clear(i);
  }
  msr.shift();
  msrx.shift();
  Serial.begin(115200);
}

void loop() {
  if (hallSensor1State || hallSensor2State) {
    if (anim == 0) {
      DisplayFrame(CDArrays[frame]);
      // Incrémenter le compteur
      frameHoldCounter++;
      // Vérifier s’il est temps de passer à la frame suivante
      if (frameHoldCounter >= frameHoldTime) {
        // Réinitialiser le compteur
        frameHoldCounter = 0;
        // Passer à la frame suivante
        frame++;
        if (frame > 21) {
          frame = 0;
          repeat++;
          if (repeat == 1) {
            repeat = 0;
            anim++;
            frameHoldTime = 1;
            repeatvalue = 1;
          }
        }
      }
    }
    if (anim == 1) {
      DisplayFrame(ImageCD_22);
      // Incrémenter le compteur
      frameHoldCounter++;
      // Vérifier s’il est temps de passer à la frame suivante
      if (frameHoldCounter >= frameHoldTime) {
        // Réinitialiser le compteur
        frameHoldCounter = 0;
        // Passer à la frame suivante
        frame++;
        if (frame > 50) {
          frame = 0;
          repeat++;
          if (repeat == repeatvalue) {
            repeat = 0;
            anim++;
            frameHoldTime = 2;
            repeatvalue = 1;
          }
        }
      }
    }
    if (anim == 2) {
      DisplayFrame(ViasionArrays[frame]);
      // Incrémenter le compteur
      frameHoldCounter++;
      // Vérifier s’il est temps de passer à la frame suivante
      if (frameHoldCounter >= frameHoldTime) {
        // Réinitialiser le compteur
        frameHoldCounter = 0;
        // Passer à la frame suivante
        frame++;
        if (frame > 21) {
          frame = 0;
          repeat++;
          if (repeat == repeatvalue) {
            repeat = 0;
            anim++;
            frameHoldTime = 1;
            repeatvalue = 1;
          }
        }
      }
    }
    if (anim == 3) {
      DisplayFrame(Image_Viasion22);
      // Incrémenter le compteur
      frameHoldCounter++;
      // Vérifier s’il est temps de passer à la frame suivante
      if (frameHoldCounter >= frameHoldTime) {
        // Réinitialiser le compteur
        frameHoldCounter = 0;
        // Passer à la frame suivante
        frame++;
        if (frame > 50) {
          frame = 0;
          repeat++;
          if (repeat == repeatvalue) {
            repeat = 0;
            anim++;
            frameHoldTime = 2;
            repeatvalue = 1;
          }
        }
      }
    }
    if (anim == 4) {
      DisplayFrame(ViasionOTRArrays[frame]);
      // Incrémenter le compteur
      frameHoldCounter++;
      // Vérifier s’il est temps de passer à la frame suivante
      if (frameHoldCounter >= frameHoldTime) {
        // Réinitialiser le compteur
        frameHoldCounter = 0;
        // Passer à la frame suivante
        frame++;
        if (frame > 11) {
          frame = 0;
          repeat++;
          if (repeat == repeatvalue) {
            repeat = 0;
            anim++;
            frameHoldTime = 1;
            repeatvalue = 10;
          }
        }
      }
    }
    if (anim == 5) {
      DisplayFrame(CatRunArray[frame]);
      // Incrémenter le compteur
      frameHoldCounter++;
      // Vérifier s’il est temps de passer à la frame suivante
      if (frameHoldCounter >= frameHoldTime) {
        // Réinitialiser le compteur
        frameHoldCounter = 0;
        // Passer à la frame suivante
        frame++;
        if (frame > 9) {
          frame = 0;
          repeat++;
          if (repeat == repeatvalue) {
            repeat = 0;
            anim++;
            frameHoldTime = 2;
            repeatvalue = 2;
          }
        }
      }
    } else if (anim == 6) {
      DisplayFrame(CatArrays[frame]);
      // Incrémenter le compteur
      frameHoldCounter++;
      // Vérifier s’il est temps de passer à la frame suivante
      if (frameHoldCounter >= frameHoldTime) {
        // Réinitialiser le compteur
        frameHoldCounter = 0;
        // Passer à la frame suivante
        frame++;
        if (frame > 61) {
          frame = 0;
          repeat++;
          if (repeat == repeatvalue) {
            repeat = 0;
            anim++;
            frameHoldTime = 2;
            repeatvalue = 3;
          }
        }
      }
    } else if (anim == 7) {
      DisplayFrame(RunningSFArrays[frame]);
      // Incrémenter le compteur
      frameHoldCounter++;
      // Vérifier s’il est temps de passer à la frame suivante
      if (frameHoldCounter >= frameHoldTime) {
        // Réinitialiser le compteur
        frameHoldCounter = 0;
        // Passer à la frame suivante
        frame++;
        if (frame > 11) {
          frame = 0;
          repeat++;
          if (repeat == repeatvalue) {
            repeat = 0;
            anim++;
            frameHoldTime = 2;
            repeatvalue = 1;
          }
        }
      }
    } else if (anim == 8) {
      DisplayFrame(Dance1Arrays[frame]);
      // Incrémenter le compteur
      frameHoldCounter++;
      // Vérifier s’il est temps de passer à la frame suivante
      if (frameHoldCounter >= frameHoldTime) {
        // Réinitialiser le compteur
        frameHoldCounter = 0;
        // Passer à la frame suivante
        frame++;
        if (frame > 93) {
          frame = 0;
          repeat++;
          if (repeat == repeatvalue) {
            repeat = 0;
            anim++;
            frameHoldTime = 1;
            repeatvalue = 4;
          }
        }
      }
    } else if (anim == 9) {
      DisplayFrame(EYEArrays[frame]);  // Incrémenter le compteur
      frameHoldCounter++;
      // Vérifier s’il est temps de passer à la frame suivante
      if (frameHoldCounter >= frameHoldTime) {
        // Réinitialiser le compteur
        frameHoldCounter = 0;
        // Passer à la frame suivante
        frame++;
        if (frame > 73) {
          frame = 0;
          repeat++;
          if (repeat == repeatvalue) {
            repeat = 0;
            anim++;
            frameHoldTime = 2;
            repeatvalue = 4;
          }
        }
      }
    } else if (anim == 10) {
      DisplayFrame(GlobexArrays[frame]);  // Incrémenter le compteur
      frameHoldCounter++;
      // Vérifier s’il est temps de passer à la frame suivante
      if (frameHoldCounter >= frameHoldTime) {
        // Réinitialiser le compteur
        frameHoldCounter = 0;
        // Passer à la frame suivante
        frame++;
        if (frame > 14) {
          frame = 0;
          repeat++;
          if (repeat == repeatvalue) {
            repeat = 0;
            anim++;
            frameHoldTime = 20;
            repeatvalue = 1;
          }
        }
      }
    } else if (anim == 11) {
      DisplayFrame(ClockArrays[frame]);  // Incrémenter le compteur
      frameHoldCounter++;
      // Vérifier s’il est temps de passer à la frame suivante
      if (frameHoldCounter >= frameHoldTime) {
        // Réinitialiser le compteur
        frameHoldCounter = 0;
        // Passer à la frame suivante
        frame++;
        if (frame > 13) {
          frame = 0;
          repeat++;
          if (repeat == repeatvalue) {
            repeat = 0;
            anim = 0;
            frameHoldTime = 2;
            repeatvalue = 1;
          }
        }
      }
    }
    hallSensor1State = 0;
    hallSensor2State = 0;
  }
}

Retrouvez l’histoire de Raspberry Pi dans cette vidéo :

YouTube video