Antoine POLA
Published © GPL3+

My own version of the Theremin

I’ve always wanted to make music without touching anything. So I decided to build a little theremin using distance sensors.

BeginnerShowcase (no instructions)10 hours38
My own version of the Theremin

Things used in this project

Hardware components

STM32 Nucleo-64 Board
STMicroelectronics STM32 Nucleo-64 Board
×1
Ultrasonic Sensor - HC-SR04 (Generic)
Ultrasonic Sensor - HC-SR04 (Generic)
×1
VL53L0X 2490
×1
SPI TFT
×1
Resistor 10k ohm
Resistor 10k ohm
×3
Resistor 330 ohm
Resistor 330 ohm
×3
LED RGB
×1
speaker SP-6120
×1

Software apps and online services

Visual Studio Code Extension for Arduino
Microsoft Visual Studio Code Extension for Arduino

Story

Read more

Code

Project code

C/C++
#include <Arduino.h>
#include "Adafruit_VL53L0X.h"
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>

Adafruit_VL53L0X lox = Adafruit_VL53L0X();

const int GENE_AUDIO = PA4;

//TFT_SPI
#define TFT_CS   D10
#define TFT_DC   D2
#define TFT_RST  D4
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);

// Broches pour la LED RGB (cathode commune)
const int LED_R = PB3;
const int LED_G = PB4;
const int LED_B = PC7;
uint32_t tempsLED = 0;


// HC-SR04
const int TRIG_PIN = PA8;
const int ECHO_PIN = PA9;


bool enBip = false; //Indique si un bip est en cours
uint32_t tempsPrc = 0; //Moment du dernier changement (début bip ou pause)
uint32_t tempsAct = 0; //Temps actuel
uint32_t prochain_BIP = 0; //gestion de la vitesse



//Variables affichage (mémoriser les dernières valeurs affichées -> éviter de redessiner inutilement)
int lastDistanceVL = -1; //-1 au départ pour indiquer qu’aucune valeur n’a encore été affichée
int lastDistanceHC = -1;
int lastFrequence = -1;
int lastIntervalle_Bip = -1;



void Couleur(int r, int g, int b) {
  analogWrite(LED_R, r);
  analogWrite(LED_G, g);
  analogWrite(LED_B, b);
}



//Fonction Affichage TFT_SPI
void afficherTFT(int distanceVL, int distanceHC, int intervalle_BIP, int frequence) {
  //VL53L0X
  if (distanceVL != lastDistanceVL) {
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_CYAN, ILI9341_BLACK);
    tft.setCursor(10, 10); tft.println("VL53:");
    tft.setTextSize(3);
    tft.setCursor(10, 40); 
    tft.fillRect(10, 40, 150, 30, ILI9341_BLACK); // efface juste la valeur
    tft.print(distanceVL); tft.println(" mm");
    lastDistanceVL = distanceVL;
  }

  //HC-SR04
  if (distanceHC != lastDistanceHC) {
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);
    tft.setCursor(170, 10); tft.println("HC:");
    tft.setTextSize(3);
    tft.setCursor(170, 40); 
    tft.fillRect(170, 40, 140, 30, ILI9341_BLACK);
    tft.print(distanceHC); tft.println(" cm");
    lastDistanceHC = distanceHC;
  }

  //Intervalle_BIP
  if (intervalle_BIP != lastIntervalle_Bip) {
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_MAGENTA, ILI9341_BLACK);
    tft.setCursor(10, 130); tft.println("BIP:");
    tft.setTextSize(3);
    tft.setCursor(10, 160); 
    tft.fillRect(10, 160, 150, 30, ILI9341_BLACK);
    tft.print(intervalle_BIP); tft.println(" ms");
    lastIntervalle_Bip = intervalle_BIP;
  }

  //Fréquence
  if (frequence != lastFrequence) {
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_GREEN, ILI9341_BLACK);
    tft.setCursor(170, 130); tft.println("Freq:");
    tft.setTextSize(3);
    tft.setCursor(170, 160); 
    tft.fillRect(170, 160, 140, 30, ILI9341_BLACK);
    tft.print(frequence); tft.println(" Hz");
    lastFrequence = frequence;
  }
}



void setup() {
  Serial.begin(115200); //Vitesse de communication 9600bits/s
  
  pinMode(GENE_AUDIO, OUTPUT); //Configure la broche P4A en mode sortie

  pinMode(TRIG_PIN, OUTPUT); //Configure HC-SR04
  pinMode(ECHO_PIN, INPUT);

  pinMode(LED_R, OUTPUT); //Configure les broches en SORTIE
  pinMode(LED_G, OUTPUT);
  pinMode(LED_B, OUTPUT);

  digitalWrite(LED_R, LOW); //Initialise à 0
  digitalWrite(LED_G, LOW);
  digitalWrite(LED_B, LOW);

  while (!Serial) { delay(1); } //Attend que le port série soit prêt

  if (!lox.begin()) { //Tente d'initialiser le capteur VL53L0X
    Serial.println(F("Failed to boot VL53L0X"));
    while(1);
  }


  //Initialisation TFT_SPI
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);
}



void loop() {
  tempsAct = millis();

  //Lecture du capteur VL53L0X (pitch)
  VL53L0X_RangingMeasurementData_t measure; //Créé une structure measure pour stocker les données de mesure
  lox.rangingTest(&measure, false); //Lance une mesure de distance avec le capteur
  
  int distanceVL = measure.RangeMilliMeter;
  distanceVL = constrain(distanceVL, 50, 500);
  int frequence = constrain(5 * (measure.RangeMilliMeter - 20) + 150, 150, 2000); //Calcule une fréquence sonore en fonction de la distance et limite la fréquence entre 150 Hz et 2000 Hz


  //LED RGB
  if (tempsAct - tempsLED > 50) {
    
    tempsLED = tempsAct;
    int r = 0, g = 0, b = 0;
    
    if (distanceVL < 200) {
    //Rouge vers Jaune
    r = 255;
    g = map(distanceVL, 50, 200, 0, 255); //50 = min, 200 = max
    b = 0;
    } else if (distanceVL < 350) {
    //Jaune vers Vert vers Cyan
    r = map(distanceVL, 200, 350, 255, 0);
    g = 255;
    b = map(distanceVL, 200, 350, 0, 255);
    } else {
    //Cyan vers Bleu
    r = 0;
    g = map(distanceVL, 350, 500, 255, 0);
    b = 255;
    }
    Couleur(r, g, b);
  }


  //Lecture du capteur HC-SR04 (vitesse)
  digitalWrite(TRIG_PIN, LOW); //fonction bloquante mais nécessaire pour pouvoir utiliser le capteur + temps négligeable
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  unsigned long duree = pulseIn(ECHO_PIN, HIGH, 30000); // timeout 30ms
  float distanceHC = duree * 0.0343 / 2.0; //d = t * v
  distanceHC = constrain(distanceHC, 10, 100);

  int intervalle_BIP;
  if (distanceHC <= 20) 
    intervalle_BIP = 100;
  else if (distanceHC <= 50) 
    intervalle_BIP = 300;
  else 
    intervalle_BIP = 800;

    
  //Démarrer un bip si intervalle écoulée
  if (!enBip && tempsAct >= prochain_BIP) {
    enBip = true;
    tempsPrc = tempsAct;
    tone(GENE_AUDIO, frequence); // tone() génère un son sur la broche GENE_AUDIO (un signal PWM à 50% de rapport cyclique)
    prochain_BIP = tempsAct + intervalle_BIP;
    Serial.print("Distance HC-SR04: "); Serial.print(distanceHC);
    Serial.print(" cm | Intervalle BIP: "); Serial.print(intervalle_BIP);
    Serial.print("ms | frequence: "); Serial.println(frequence);
  }

  //Arrêter le bip après 100 ms
  if (enBip && (tempsAct - tempsPrc >= 100)) {
    enBip = false;
    noTone(GENE_AUDIO); //stop bip
  }

  //TFT_SPI
  afficherTFT(distanceVL, distanceHC, intervalle_BIP, frequence);
}

Credits

Antoine POLA
1 project • 1 follower
Student in Toulouse at Paul Sabatier in GEII (Electrical Engineering and Industrial Computing)

Comments