Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
mac70
Published © CC BY-NC

Star Wars Inspired DIY Volumetric Display

The goal of my project was to create a kind of DIY volumetric display that can be built at home with available components for makers.

AdvancedFull instructions providedOver 1 day3,698
Star Wars Inspired DIY Volumetric Display

Things used in this project

Hardware components

Arduino Mega 2560
Arduino Mega 2560
×1
Custom PCB
Custom PCB
×1
projector
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

STL files

also on : https://www.printables.com/de/model/506978

Schematics

schematics

for custom PCB connected to Arduino MEGA

Code

Volumetric14.ino - the main module

C/C++
The main module "Volumetric14.ino" contains the 3 main functions :
- Analyze the projector timing
- Drive the voice coils
- Control the shutter
/*
Volumetric Display Project
==========================

History
(older experimental versions not listed)
V1.0  10.10.22 first functional verison based on UNO, wavetable implemented, projector sync input connected
V1.1  28.12.22 Lightvalve circuit based on L293, Serial Control for W1070 projector added
V1.2  26.03.23 Change to MEGA after Memory was exhausted on UNO , OLED display added
V1.3  21.04.23 Rotary Encoder with Menu and Buttons for font panel added
V1.4  10.06.23 Final version

Description :
  Arduino MEGA script for my DIY Volumetric display project 
  Tasks :
  - analyze the projector timing (measure the color wheel sync signal)
  - drive the voice coils to move the projection surface 
  - flip the light valve in sync with the timing
  - provide a control interface to a PC (-> in module SerialCommand.ino)
  - remote control projector via 2nd serial interface (-> in module ProjectorRemote.ino)
  - handle front panel elements : OLED display, rotary knob, LEDs and buttons  (-> in module FronPanelControls.ino)

Connections :
 - Projector-Sync Input on Pin D18 <--- Color-Wheel-Sensor signal (from Electronics)
 
 - Lowpass Output from PWM-OUT Pin D3: 
  fc=1/(2*PI*R*C)  R=10k  C=10nF -> Fc=1.5kHz
  PWM --R--+--> Out to Audio Amp for Coil driver  
             C
           GND

 - UART2 conncections to MAX232 circuit for serial W1070 projector control :
  TXD-> 16 TX1
  RXD-> 17 RX2

 - Buttons :
  ... D4..D13 

Info on PWMs Timers:                
 0 (controls pin 13, 4);
 1 (controls pin 12, 11);
 2 (controls pin 10, 9);
 3 (controls pin 5, 3, 2);
 4 (controls pin 8, 7, 6);

Maker Mac 
June 2023

*/
 
const String Version = "1.4";     // Version of this module

#include "sinus.h"            // Sine wave-table 256 values

#include <Wire.h>             // I2C Communication library
#include <Adafruit_SSD1306.h> // OLED display based on 1306
#include <Adafruit_GFX.h>     // graphics functions
#include "Adafruit_seesaw.h"  // Adafruit Rotary Encoder 
#include <seesaw_neopixel.h>  // Neopixel on encoder 

#define OLED_ADDR     0x3C    // I2C address of OLED module
#define SCREEN_WIDTH   128    // OLED display width,  in pixels
#define SCREEN_HEIGHT   64    // OLED display height, in pixels
#define SEESAW_ADDR   0x36    // I2C address of encoder module
#define SS_SWITCH       24    // Switch of Encoder
#define SS_NEOPIX        6    // Neopixel of Encoder

// rebel logo bitmap
#define LOGO_WIDTH    128     // logo dimensions
#define LOGO_HEIGHT   64
#include "rebel_logo.h"

// Pin Assigment 
#define PWM_OUT         3           // PWM output for wave-gen out (D3 PWM pin controlled by Timer3) 
#define Proj_PulsePin  18           // input for Projector sync pulse 
#define L293A1         14           // L293 1A for Lightvalve control
#define L293A2         15           // L293 2A for Lightvalve control
#define PanelLED  2                 // all Buttons (input) and LEDs on front panel
#define Button0   4   
#define LEDbtn0   5
#define Button1   6
#define LEDbtn1   7
#define Button2   8
#define LEDbtn2   9
#define Button3   10
#define LEDbtn3   11
#define Button4   12
#define LEDbtn4   13
#define LEDon     255               // define max brighthness for LEDs (needs to be 255 for PWMs sharing Timer4!)

// variables

// set default phase here :
volatile unsigned int wavetable_start=50; // startpoint for wavetable read after sync pulse (determines the phase shift)
volatile unsigned int wavetable_end=49;   // end of wavetable read (=  wavetable_start - 1)

unsigned int wavetable_entries=256;  // number of values in wavetable 
volatile unsigned int wavesample=0;  // index of current entry in wavetable for output
volatile unsigned long PeriodBetweenPulsesProj = 1000000; // Time in us between Projector IR sync pulses
volatile unsigned long LastMeasuredProj;                  // last measurement
volatile boolean update_PulseProj = false;                // shows if a pulse signal was measured recently
float fps_proj;                                           // current projector wheel input fps
volatile unsigned int divider=4;        // divider after N color wheel pulses to trigger new frame 
volatile unsigned int divcount=1;        // counter for pulses 
volatile boolean proj_sync_mode=true;   // true if operation is synced to projector pulses, if false continous operation 
volatile unsigned int valves = 0;      // counter until next valve change
volatile boolean valve_alt = 1;        // for control of alternating block voltages
volatile unsigned int valves_disable = 31; // number of wavetable entries when valves block (position = Ts * valves_disable)
volatile unsigned int valves_enable = 58; // number of wavetable entries when valves open

unsigned long freq = 30;      // desired output freq for coils -> here, normally 30 Hz 
unsigned long fs ;            // freq*wavetable_entries -> output freq * 256 steps of wavetable give sample freq fs
unsigned long Ts ;            // Ts = 1000000/fs;  ->  sample-time Ts=1/fs , value to be set to Timer4 (e.g. 130us for 30Hz)  

boolean output_enabled = false;   // true if wave output is on or off
boolean logfps = false;           // true if value logging is enabled
boolean projector_on = false;     // true if projector has been powered on 
boolean projector_off = false;    // true if projector has been completely powered off (on/off inbetween states are possible!) 
boolean projector_sbs = false;    // true if projector set to sbs mode 
volatile byte output;             // output for PWM value 0-255
unsigned int proj_input = 1;      // selected input of projector (1=VGA ; 2=HDMI1 ; maybe more later ?)

// Host Serial command interface
boolean cmdreceived = false;    // true if new command received
String inputString = "";        // contains the received commandline
String projcommand = "";         // command send string for projector
String projresponse = "";        // answer from projector after command

// User Interface 
const unsigned int menusize=6;   // number of defined menu items including 0 (0..size-1)
const String menuitem[] = { "0.Status", "1.Freq", "2.Phase", "3.LV open", "4.LV close", "5.Set SBS", "6.Input" };  // menu items
signed int menu_position=0;      // current selected menu-item
unsigned int menu_timeout;       // timeout to clear menu when no interaction
signed int encoder_position;  // holds current encoder position
const int MENUTIME=30;           // timeout 3 sec 
unsigned int errorLED_timeout=0;  // timeout to clear Error LED
const int ERRORTIME=100;           // timeout 10 sec

// objects connected to I2C
Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
Adafruit_seesaw ss;
seesaw_NeoPixel sspixel = seesaw_NeoPixel(1, SS_NEOPIX, NEO_GRB + NEO_KHZ800);


/* --------------------------------------------------------------------------------------
 Setup after Reset
-------------------------------------------------------------------------------------- */
void setup()
{ 
  int t ; // i,j ; 
  boolean initOK=true;
  
  // init IOs

  // Buttons + LEDs 
  pinMode(PanelLED, OUTPUT);
  analogWrite(PanelLED, 0);    
  pinMode(Button0 , INPUT_PULLUP);
  pinMode(LEDbtn0 , OUTPUT);
  analogWrite(LEDbtn0, 0);              
  pinMode(Button1 , INPUT_PULLUP);
  pinMode(LEDbtn1 , OUTPUT);
  analogWrite(LEDbtn1, 0);              
  pinMode(Button2 , INPUT_PULLUP);
  pinMode(LEDbtn2 , OUTPUT);
  analogWrite(LEDbtn2, 0);
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW); 

  // Wave-Output PWM pin 
  pinMode(PWM_OUT, OUTPUT);                   
 
  // Sync-Input from Projector
  pinMode(Proj_PulsePin, INPUT);                              

  // LightValve driver pins connected to L293 chip
  pinMode(L293A1, OUTPUT);
  digitalWrite(L293A1, 0);
  pinMode(L293A2, OUTPUT);
  digitalWrite(L293A2, 0);

   // Open Host-IF serial0 (built-in interface) for UART via USB to PC for control interface
  Serial.begin(9600);
  t = 50; //wait for serial port to open, max 5 seconds
  while (!Serial) {
      delay(100);
      if ( (t--) == 0 ) {
        initOK=false;
        break;
      }
  }
  // Output Initial message 
  Serial.print(F("\nVolumetric Display\n")); 
  Serial.println(F("==================")); 
  Serial.print(F("Version ="));
  Serial.println(Version);
  Serial.println();

  // initialize OLED display with OLED_ADDR address (normally 0x3C) 
  Serial.print(F("Searching for OLED Display..."));
  if (!oled.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
    Serial.println(F("ERROR : OLED module allocation failed !!!"));
    initOK=false;
  }
  Serial.println(F("OLED module found."));

  // initialize Rotary Encoder module with SEESAW_ADDR
  Serial.print(F("Searching for Encoder..."));
  if (! ss.begin(SEESAW_ADDR) || ! sspixel.begin(SEESAW_ADDR)) {
    Serial.println(F("ERROR : Couldn't find Encoder on default address !!!"));
    initOK=false;
  }
  Serial.println(F("found Encoder."));
  // check Encoder firmware
  uint32_t version = ((ss.getVersion() >> 16) & 0xFFFF);
  if (version  != 4991){
    Serial.print("Warning : Wrong firmware for Encoder loaded? ");
    Serial.println(version);
  }
  // for the Neopixel on the encoder... 
  sspixel.setBrightness(40);  // ...set brightness 0..255
  sspixel.show();
  // get starting position
  encoder_position = ss.getEncoderPosition();

  delay(250);         // wait for initializing

  // Show Start Message on OLED
  show_main_page();
  oled.setTextSize(1);          
  oled.setCursor(0, 52);        
  oled.print("     Version=");
  oled.println(Version);
  oled.display();      

   // tesing all LEDs
     for (int j=0; j<255; j++) {   
      delay(5);
      analogWrite(LEDbtn0, j);
      analogWrite(LEDbtn1, j);
      analogWrite(LEDbtn2, j);
      analogWrite(PanelLED, j);    
    }                        
     for (int j=255; j>0; j--) {   
      delay(5);
      analogWrite(LEDbtn0, j);
      analogWrite(LEDbtn1, j);
      analogWrite(LEDbtn2, j);
      analogWrite(PanelLED, j);    
    }     

    // set LEDs to default values                   
   analogWrite(LEDbtn0, 0);      // Proj is off
   analogWrite(LEDbtn1, 0);      // Wave if off
   analogWrite(LEDbtn2, LEDon);  // Sync is on 
   analogWrite(PanelLED, 0);     // Error-LED is off


  // Open Serial2 for Projector communication
  Serial2.begin(19200);  
  t = 50; //wait for serial port to open, max 5 seconds
  while (!Serial2) {
      delay(100);
      if ( (t--) == 0 ) {
        initOK=false;
        Serial.println(F("ERROR : Could not open serial port to projector!"));
        break;
      }
  } 

  if (initOK) Serial.println(F("Intit done.")); else Serial.println(F("Warning : Initialization errors !!"));
  
  // Start Projector via Serial2 interface, wait for Power on and do settings
  TurnProjectorOn();

  // show commands on host serial IF
  inputString="help";
  getCommand();
  inputString="";
  Serial.println(" ");

  // Setup PWM output for wavetable generator
  pinMode(PWM_OUT, OUTPUT);                   // Set PWM-Out pin (PWM-Pin D3 controlled by Timer 3 on MEGA)
  TCCR3B = TCCR3B & 0b11111000 | 1;           // set 31Khz as PWM frequency  
  analogWrite(PWM_OUT, 0);                    // output off
 
  // Timer4 Setup (controls the wavetable output)
  freq=30;
  fs = freq*wavetable_entries ; // output freq * 256 steps of wavetable give sample freq fs
  Ts = 1000000/fs;              // sample-time Ts=1/fs  default is 130us for 30Hz*256 (values in us)
  SetTimer4((int)Ts);           // Set Timer4 to  .. us
 
  // Pulse-sync-input from Projector : Enable interrupt when going from H->L
  pinMode(Proj_PulsePin, INPUT);  // or INPUT_PULLUP if signal is bad                            
  attachInterrupt(digitalPinToInterrupt(Proj_PulsePin), Proj_Pulse_Event, FALLING);  

  sei(); //allow interrupts

  Serial.println(F("\n\nEnd of Startup. \nMay the force be with you."));
  Serial.println("\n>");
  
} 


/* --------------------------------------------------------------------------------------
 MAIN LOOP (do some foreground stuff, but main action is handled via interrupts)
-------------------------------------------------------------------------------------- */
void loop() 
{ 
 int t;
   
    user_input();  // check and process buttons (-> in module FronPanelControls.ino)

    if (Serial.available() > 0) processInput(); // process serial commands (-> in module SerialCommand.ino)
    
    if (menu_timeout >0)  // handle menu timeout
    {
      menu_timeout--;
      if (menu_timeout == 0) show_main_page();  // clear menu after timeout and show main page
    }

   if (errorLED_timeout >0)  // handle error LED timeout
   {
     errorLED_timeout--;
     analogWrite(PanelLED, (errorLED_timeout*5));     // dim Error LED gradually
     if (errorLED_timeout == 0) analogWrite(PanelLED, 0);     // Error-LED is off
   }

    // calc current projector sync input
    if (!update_PulseProj)PeriodBetweenPulsesProj = 1000000;
    fps_proj=1000000*(1/(float)PeriodBetweenPulsesProj);   

    if (logfps)  // if enabled, dump some debug data to the host IF 
     {
          // output logging data:
          Serial.print(F("\tFproj="));
          Serial.print(fps_proj);
          Serial.print(F("\tTproj="));
          Serial.print(PeriodBetweenPulsesProj);
          Serial.print(F("\tFout ="));
          Serial.print(freq);
          Serial.print(F("\tTs  ="));
          Serial.print(Ts);
          Serial.print(F(" us "));
          Serial.print(F("\tPhase  ="));
          Serial.print(wavetable_start);
          Serial.print(F("\tvalve block  ="));
          Serial.print(valves_disable);
          Serial.print(F(" ... "));
          Serial.print(valves_enable);                 
          Serial.println(" ");
     } 
     
    delay(50);  // some delay (uses Timer0 !)

}



/* --------------------------------------------------------------------------------------
 Setup Timer4 to interrupt every ..us (using CTC Mode)
 Timer4 ticks are used for wavetable output and valve control
-------------------------------------------------------------------------------------- */
void SetTimer4(unsigned int us)
{
 
// Timer4 setup 
 TCCR4B = 0;                  // stop timer
 TCCR4A = 0;                  // disconnect OCR pins
 TCNT4  = 0;                  //initialize counter value to 0
 TIMSK4 = (1 << OCIE4A);      // enable timer compare interrupt

 // set reload value in us with 16MHz prescaler :
 OCR4A= (16 * us)-1;          // = (16*10^6 / 1) * (us*10^-6) - 1  = (16*us)-1;

 TCCR4B = (1 << CS40) | (1 << WGM42);   // turn on Timer4 (in CTC mode and prescaler = 1 = 16Mhz)
 
 Serial.print(F("\t timer4 set to us= "));  // for debug output only
 Serial.print(us);

}

/* --------------------------------------------------------------------------------------
 Timer4 Interrupt -> wavetable output and valve control
-------------------------------------------------------------------------------------- */
ISR(TIMER4_COMPA_vect)
{

  if (output_enabled)
  {
    // wavetable output control
    output=waveform[wavesample];       // read and output next wavetable entry to PWM pin
    analogWrite(PWM_OUT, output); 
    wavesample++;
     if (wavesample==wavetable_end) // end of wavetable readout section reached ?
     {
       if (proj_sync_mode) TCCR4B = 0;   // stop Timer in projector sync mode 
       wavesample=wavetable_start;    // next time re-start from readout start pos
       

     }
     if (wavesample==wavetable_entries)  wavesample=0;  // go back to start of table


     // Lightvalve control 
     valves++;
     if (valves == valves_enable)  //  lightvalve open (transparent)
     {
        digitalWrite(L293A1, 0);
        digitalWrite(L293A2, 0);
     }
     if (valves == valves_disable)  // (using alternating polarity) -> lightvalve blocking (opaque)
     {
        if (valve_alt)
        {
          digitalWrite(L293A1, 1);
          digitalWrite(L293A2, 0);
          valve_alt=false;
        }
        else
        {
          digitalWrite(L293A1, 0);
          digitalWrite(L293A2, 1);
          valve_alt=true;
        }

     }

     // -------  war == 64
     if (valves>=63) valves=0;  // re-start valve switch control after 1/4 cycle

  }  

} 


/* --------------------------------------------------------------------------------------
 Sync Puls Interrupt - triggered by falling edge of interrupt pin
 triggered by Color wheel sensor after one rotation = BRGBRG sequence typ each 8.3ms (120Hz)  
---------------------------------------------------------------------------------------- */
void Proj_Pulse_Event()
{
unsigned long mnow2;
    
   mnow2=micros();
   PeriodBetweenPulsesProj = mnow2 - LastMeasuredProj;           // get current period (time between 2 sync pulses) 
   LastMeasuredProj = mnow2;
   update_PulseProj = true; 
 
   divcount++;

   if (divcount > divider)    // after N color wheel pulses, re-start wavetable  (e.g. 4x 8.3ms = 33.3ms) 
   {
    divcount = 1;
    valves=0;
    TCNT4  = 0;
   if (output_enabled) TCCR4B = (1 << CS40) | (1 << WGM42);   // start wavetable player timer (enable Timer4)
   }

}

/* --------------------------------------------------------------------------------------
 Enable/disable Sin-Wave output (PWM pin) to amplifier (triggered by panel button or serial cmd)  
---------------------------------------------------------------------------------------- */
void WaveON(void)
{
      Serial.println(F("\n---  WAVE ON"));
      analogWrite(LEDbtn1, LEDon);          // light up WAVE button

      wavesample=wavetable_start;            // set start of table readout
      TCCR4B = (1 << CS40) | (1 << WGM42);   // start Timer4   
      output_enabled=true; 

      show_main_page();
      oled.setTextSize(1);          
      oled.setTextColor(WHITE, BLACK); 
      oled.setCursor(0, 53);        
      oled.println("          ON       ");
      oled.display();       

}

void WaveOFF(void)  
{
      Serial.println(F("\n---  WAVE OFF"));
      analogWrite(LEDbtn1, 0);           // turn off WAVE button LED    

      TCCR4B = 0;                           // stop Timer4 
      output_enabled=false;
      digitalWrite(L293A1, 0);             // set lightvalves to open 
      digitalWrite(L293A2, 0);    

      show_main_page();

}


/* --------------------------------------------------------------------------------------
 sets a new start for wavetable-readout = phase-shift 
---------------------------------------------------------------------------------------- */
void SetPhase(unsigned int ph)
{
        if (output_enabled && proj_sync_mode)  // during normal operation, change gradually to have a ...
        {            
            while(wavetable_start != ph)   // ...smooth transisiton until new setpoint reached (otherwise interupts the movement)
            {
              if (wavetable_start==0) 
              { 
                wavetable_start=wavetable_entries; 
                wavetable_end=wavetable_start-1;
              }
              else
              {
                wavetable_start--;  // going backwards leads to less interference
                wavetable_end=wavetable_start-1;
              }
                delay(8);
            }
        }else
        {
          wavetable_start=ph;
          if (wavetable_start==0) wavetable_end=256; else wavetable_end=wavetable_start-1; 
          wavesample=wavetable_start; 
        }
}

SerialCommand.ino

C/C++
Handling serial communication to PC
/* --------------------------------------------------------------------------------------
Volumetric Display Project
Host Serial Command Interface 
this module contains all serial interface functions for a connected PC 
MakerMac
May 23 
-------------------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------------------
Process Input -> Called from main() when serial characters have been received
-------------------------------------------------------------------------------------- */
void processInput() 
{
  while (Serial.available())   // process all characters in input buffer received 
  {
    char inChar = (char)(Serial.read());  // read next character
    if (inChar == '\n')                   // Line-Feed received ?
       getCommand();                      // yes, commandline complete -> process command
    else
    {
       inputString += inChar;             // no, add chat to inputString 
       Serial.print(inChar);              // Echo 
    }
  }
}

/* --------------------------------------------------------------------------------------
Process Commands - evaluate and execute received commands
-------------------------------------------------------------------------------------- */
void getCommand()  
{
   String paramString = "";         // Parameters
   int len = inputString.length();  // length of commandline
   int s,md,t;
   logfps=false;  // stop logfps output if enabled

       
    // --------------------------------------
    // helpscreen
    if (inputString.startsWith("help"))
    {
        Serial.println("");
        Serial.println(F("available commands :"));
        Serial.println(F("help           ...lists all commands"));
        Serial.println(F("on/off         ...turn wave output ON or OFF "));
        Serial.println(F("mode [N]       ...sets mode to proj_sync (0) or continous (1) "));
        Serial.println(F("log            ...logs values (until next CR)"));
        Serial.println(F("freq [N]       ...sets frequency "));
        Serial.println(F("div [N]        ...sets divider "));
        Serial.println(F("von  [N]       ...Lightvalve on position or 256=Valves OFF "));
        Serial.println(F("voff [N]       ...Lightvalve off position  "));
        Serial.println(F("shift [N]      ...shift wavetable start by N samples "));
        Serial.println(F("skip           ...skip one wheel sync (to find startpos) "));
        Serial.println(F("proj status    ...shows projector status info"));
        Serial.println(F("3d on/off      ...set projector SBS mode on or off"));
        Serial.println(F("proj [on/off]  ...switches projector on/off "));

 
    }   
      
    // --------------------------------------
    // wave output ON/OFF
    if (inputString.startsWith("on"))
          WaveON();
    if (inputString.startsWith("off"))
          WaveOFF();

    // --------------------------------------
    // set mode (0=proj sync ; 1=free running)
    if (inputString.startsWith("mode"))
    {
   
      paramString = inputString.substring(5,len);
      md=paramString.toInt();
      if (md == 0)  // proj sync mode
      {  
         proj_sync_mode=true; 
         analogWrite(LEDbtn2, LEDon);  // light up SYNC button
         Serial.println(F("Mode 0 (SYNC) enabled"));
      }
      else if (md == 1)  // free running mode
      {
         proj_sync_mode=false; 
         wavesample=0;
         TCCR4B = (1 << CS40) | (1 << WGM42);   // start Timer4   
         output_enabled=true;
         analogWrite(LEDbtn2, 0);  // SYNC button off      
         Serial.println(F("Mode 1 (free running) enabled"));
      }
      else
      {
        Serial.println(F(" error, invalid  value !"));
      }
    }


    
    // --------------------------------------
    // print logfps information
    if (inputString.startsWith("log"))
    {
      Serial.println(F("\n--- start logging values :"));
      logfps = true;
    }
    

    // --------------------------------------
    // skip one wheelsync
    if (inputString.startsWith("skip"))
    {
      Serial.println(F("\n--- skipping one wheel sync pulse"));
      divcount--;
    }

    // --------------------------------------
    // set divider
    if (inputString.startsWith("freq"))
    {
      TCCR4B = 0;                           // stop Timer4 
      output_enabled=false;
      
      paramString = inputString.substring(5,len);
      freq=paramString.toInt();
      if (freq >0)
      {     
        wavesample=0;
        
        Serial.print(F("\n--- freq  ="));
        Serial.print(freq);
        fs = freq*wavetable_entries ; // output freq * 256 steps of wavetable give sample freq fs
        Ts = 1000000/fs;              // sample-time Ts=1/fs  e.g. 39us for 100Hz*256 (values in us)

        SetTimer4((int)Ts);  // Set Timer to  .. us
        Serial.print(" Ts  =");
        Serial.print(Ts);
        Serial.print(F(" us "));

        Serial.println(F(" ...ok"));
        output_enabled=true;
      }
      else
      {
        Serial.println(F(" error, invalid  value !!"));
      }
    }

    // --------------------------------------
    // set divider
    if (inputString.startsWith("div"))
    {
      paramString = inputString.substring(4,len);
      divider=paramString.toInt();
      if (divider >0)
      {     
        Serial.print(F("\n--- set div ="));
        Serial.print(divider);
        Serial.println(F(" ...ok"));
      }
      else
      {
        Serial.println(F(" error, invalid divider value !!"));
        divider = 4;
      }
    }
    
    // --------------------------------------
    // shift
    if (inputString.startsWith("shift"))
    {
      paramString = inputString.substring(6,len);
      t=paramString.toInt();
      if (t >=0)
      { 
        Serial.print(F("\n--- shift wavetable start to "));
        Serial.print(t);
        SetPhase(t);
        Serial.println(F(" ...ok"));

      }
      else
      {
        Serial.println(F(" error, invalid value !!"));
      }
    }
    
    // --------------------------------------
    // set Lightvalves
    if (inputString.startsWith("von"))
    {
      paramString = inputString.substring(4,len);
      s=paramString.toInt();
      if ((s >0) && (s<300))
      {     
        Serial.print(F("\n--- valve on ="));
        Serial.print(s);
        Serial.println(F(" ...ok"));
        valves_enable=s;
        if (s>=256) 
        { 
          Serial.print(F("\n--- valves OFF"));

        digitalWrite(L293A1, 0);
        digitalWrite(L293A2, 0);    
        }
      }
      else
      {
        Serial.println(F(" error, invalid value !!"));
      }
    }

   if (inputString.startsWith("voff"))
    {
      paramString = inputString.substring(5,len);
      s=paramString.toInt();
      if ((s >0) && (s<300))
      {     
        Serial.print(F("\n--- valve off ="));
        Serial.print(s);
        Serial.println(F(" ...ok"));
        valves_disable=s;
      }
      else
      {
        Serial.println(F(" error, invalid value !!"));
      }
    }

    // --------------------------------------
    // projector commands
    if (inputString.startsWith("proj status"))
    {
      Serial.println(F("\n--- Projector Status information:"));
      GetProjectorPowerStatus();
      projcommand="*modelname=?#";
      DoProjectorCommand();
      Serial.print(F("Model ")); Serial.println(projresponse);
      projcommand="*ltim=?#";
      DoProjectorCommand();
      Serial.print(F("Lamp hours ")); Serial.println(projresponse);
      projcommand="*lampm=?#";
      DoProjectorCommand();      
      Serial.print(F("Lamp mode ")); Serial.println(projresponse);
      projcommand="*3d=?#";
      DoProjectorCommand();      
      Serial.print(F("3d mode ")); Serial.println(projresponse);
 
    }

    if (inputString.startsWith(F("proj off")))
    {
      Serial.println(F("\n--- Switching projector off, please wait..."));
      TurnProjectorOff();
     }
    if (inputString.startsWith("proj on"))
    {
      Serial.println(F("\n--- Projector restart"));
      TurnProjectorOn();
    }
    
    // 3d mode
    if (inputString.startsWith("3d on"))
    {
      Serial.println(F("\nSetting 3D Mode to Side-by-side"));
      projcommand="*3d=sbs#";
      DoProjectorCommand();
      Serial.println("Response='"+projresponse+"'");
    }
    if (inputString.startsWith("3d off"))
    {
      Serial.println(F("\nDisable 3D Mode"));
      projcommand="*3d=off#";
      DoProjectorCommand();
      Serial.println("Response='"+projresponse+"'");

    }
    // print power state
    if (inputString.startsWith("power?"))
    {
      GetProjectorPowerStatus();
    }

    // --------------------------------------
    // ready for next command
    Serial.println(F("\n>"));
    inputString = "";     
 
}

ProjectorRemote.ino

C/C++
Handling serial communication to projector
/*
Volumetric Display Project
Remote control projector functions using 2nd serial interface

Projector Serial Interface (for remote control of W10170 Projector via Software UART)

Maker Mac 
May  2023

*/



/* --------------------------------------------------------------------------------------
DoProjectorCommand
Sends a command via AltSerial UART and waits for response message ending with # from the
W1070 projector (see Benq "Projector RS232 Command Guide" for details)
command to be sent is stored in "projcommand"
response from projector will be stored in "projresponse" 
in case of known errors, the responses are :
*Illegal format
*Unsupported item
*Block item
!Timeout
-------------------------------------------------------------------------------------- */
void DoProjectorCommand()
{


  char inChar;
  String ReceiveLine;
  bool complete=false;
  int rechash=0;
  unsigned int i=0 , timeout=100000;  

  projresponse="";
  while(Serial2.available()) inChar = (char)(Serial2.read());      // wait for silence
  Serial2.print('\r'+projcommand+'\r');  // send out command in format : <CR>COMMAND<CR>
  while(!complete)   // process response
  {   

      if (Serial2.available())   // process all characters in input buffer received 
      {
        inChar = (char)(Serial2.read());  // read next character until delimiter
 //       Serial.print(inChar);                // Echo everthing on built-in serial for debug 
        if (inChar == '#')                   // "#"" = end of command received ?
        {
          rechash++;                         // count the #s...
          if (rechash==2) complete=true;     // first "#" is end of command Echo, 2nd "#" means message completed 
        }
        else
        {
           if ((rechash>0) && (inChar>31)) projresponse += inChar;   // after Echo, add char to responseString (no control chars)           
        }
      }
    i++;
    if (i>timeout) 
    {
//      Serial.println("ERROR-TIMEOUT");  // for debug only
      complete=true;
      projresponse="!Timeout";
    }
  }


}


/* --------------------------------------------------------------------------------------
Gets the power status from projector 
returns: 1=powered on ; 2=not yet powered on; 3=off ; 4=no response; 5=comm error
-------------------------------------------------------------------------------------- */
int GetProjectorPowerStatus(void)
{
 //     Serial.println(F("\nGetting projector power status"));  // for debug only

      projcommand="*pow=?#";  // ask projector about power status
      DoProjectorCommand();
      // process known responses 
      if (projresponse.startsWith("*POW=ON"))
      {
        Serial.println(F("Projector is switched ON"));
        projector_on=true;
        projector_off=false;
        return 1;
      }
      else if (projresponse.startsWith("*Block item"))
      {
        Serial.println(F("Projector is NOT READY (yet)"));
        projector_on=false;
        return 2;
      }
      else if (projresponse.startsWith("*POW=OFF"))
      {
        Serial.println(F("Projector is switched OFF"));
        projector_on=false;
        projector_off=true;
        return 3;
      }
      else if (projresponse.startsWith("!Timeout"))
      {
        Serial.println(F("Projector is not responding -> No AC Power !?"));
        projector_on=false;
        return 4;
      }
      else
      {
       Serial.println("Error - unexpected response:"+projresponse);       
       return 5;
      }

}


/* --------------------------------------------------------------------------------------
 Turn on Projector 
attempts to turn on power via serial connection
-------------------------------------------------------------------------------------- */

void  TurnProjectorOn(void)
{
  int i,j, tries=0;
  char inChar;

  // message on OLED
  oled.setTextSize(1);          
  oled.setTextColor(WHITE, BLACK);     
  oled.setCursor(0, 52);        
  oled.println("Starting Projector..."); 
  oled.display();   
  analogWrite(PanelLED, 0);    // turn off ERROR LED
  errorLED_timeout=0;      

  while (Serial.available() > 0) inChar = (char)(Serial.read());  // make sure all chars received

  Serial.println(F("\nPower sequence start. Getting projector power status - for abort press any key")); // gets power status from projector 
  i=GetProjectorPowerStatus();
  while ((i!=1) && (tries<20))  // tries up to 120 times (1 each second) until power on
  {
    Serial.print(tries+1); Serial.print("/20 ");
    i=GetProjectorPowerStatus();
    if (i==3) // if Power=OFF, try to switch on
    {
       Serial.println(F("trying to switch on... "));
       delay(200);
       projcommand="*pow=on#";  
       DoProjectorCommand();
       Serial.println("response:"+projresponse);
       delay(100);
    }
    tries++;
    delay(500);
    
    oled.fillRect((tries-1)*6, 60, 6, 4, WHITE); // draw progress bar
    oled.display();
          
    analogWrite(LEDbtn0, LEDon);   // flash ON Button LED
    delay(500);
    analogWrite(LEDbtn0, 0);
    
    if ((Serial.available() > 0) || (! ss.digitalRead(SS_SWITCH)))  {     // abort when any char was received or encoder button pressed
      inChar = (char)(Serial.read());
      Serial.println("aborted...");
      tries=21;
      oled.setTextSize(1);          
      oled.setCursor(0, 52);        
      oled.println("Proj start aborted..."); 
      oled.display(); 
      analogWrite(PanelLED, LEDon);    // turn on ERROR LED
      errorLED_timeout=ERRORTIME;      // start Error LED timeout

    }
  }
  if (tries==20) // timeout ...no response from projector after 20 sec
  {
    Serial.println(F("giving up..."));  
    oled.setTextSize(1);          
    oled.setCursor(0, 52);        
    oled.println("ERROR:PROJ TIMEOUT !!"); 
    oled.display(); 
    analogWrite(PanelLED, LEDon);    // turn on ERROR LED
    errorLED_timeout=ERRORTIME;      // start Error LED timeout
  }
  else { 
     if (projector_on)  // succes -> projector responded "power on"...
     {
      oled.setTextSize(1);          // text size
      oled.setCursor(0, 52);        // position to display
      oled.println("Power on OK...       "); // text to display
      oled.display();   // show 
      analogWrite(LEDbtn0, LEDon);   // light ON Button LED

      tries=0;
      while ((!projector_sbs) && (tries<20))  // tries up to 20 times (1 each second) to set mode
      {
       projcommand="*3d=sbs#";  // ...now put projector in side-by-side more for 120Hz input
       DoProjectorCommand();
       Serial.println("response:"+projresponse);
       if (projresponse.startsWith("*3D=SBS")) // process response
       {
        Serial.println(F("OK - Projector on and in SBS mode.")); 

        oled.setTextSize(1);          
        oled.setCursor(0, 52);        
        oled.println("OK. Proj ON + SBS    "); 
        oled.display();    

        projector_sbs=true;
       }
       else if (projresponse.startsWith("*Block item"))
       {
        Serial.println(F("Projector is NOT READY (yet)"));
        delay(1000);
       }
       tries++;
       if (tries==20) // timeout after 20sec
       {
         Serial.println(F("ERROR - Projector could NOT be set to SBS mode!"));
          oled.setTextSize(1);          
          oled.setCursor(0, 52);        
          oled.println("ERROR:SBS MODE FAIL !"); 
          oled.display();  
          analogWrite(PanelLED, LEDon);    // turn on ERROR LED
          errorLED_timeout=ERRORTIME;      // start Error LED timeout  
       }
      }
     }


     Serial.println(F("ready."));
  }

  oled.fillRect(0, 60, 127, 4, BLACK); // clear progress bar
  oled.display();    

}

// Turn OFF
void  TurnProjectorOff(void)
{
  int i,j, tries=0;
  
  // message on OLED
  oled.setTextSize(1);          
  oled.setTextColor(WHITE, BLACK);     
  oled.setCursor(0, 52);        
  oled.println("Turning off Projector "); 
  oled.display();   

  Serial.println(F("\nGetting projector power status")); // gets power status from projector 
  i=GetProjectorPowerStatus();
  while ((i!=3) && (tries<256))  // tries up to 256 times (1 each second = 4 minutes) until power off
  {
    Serial.print(tries+1); Serial.print("/256 ");
    i=GetProjectorPowerStatus();
    if (i==1) // if Power=ON, try to switch off
    {
       Serial.println(F("switching off..."));
       delay(200);
       projcommand="*pow=off#";  
       DoProjectorCommand();
       Serial.println("response:"+projresponse);
       delay(100);
    }
    tries++;
    delay(500);

    if (tries<128)  // draw progress bar
      oled.fillRect(tries, 60, 1, 4, WHITE); 
    else
      oled.fillRect(256-tries, 60, 1, 4, BLACK); 
    oled.display();

    analogWrite(LEDbtn0, LEDon);   // flash ON Button LED
    delay(500);
    analogWrite(LEDbtn0, 0);

  }
  if (tries==256) // timeout ?
  {
    Serial.println(F("giving up..."));  

    oled.setTextSize(1);          
    oled.setCursor(0, 52);        
    oled.println("ERROR:PROJ TIMEOUT !!"); 
    oled.display(); 
    analogWrite(PanelLED, LEDon);    // turn on ERROR LED
    errorLED_timeout=ERRORTIME;      // start Error LED timeout     
  }
  else  // no
  { 
     if (projector_off)  // succes -> projector responded "power off"...
     {
       Serial.println(F("OK - Projector switched off.")); 
       oled.setTextSize(1);          
       oled.setCursor(0, 52);        
       oled.println("OK..Projector now off"); 
       oled.display();    
       projector_sbs=false;
       analogWrite(PanelLED, 0);    
       analogWrite(LEDbtn0, 0);
       errorLED_timeout=0;      
     }

     Serial.println(F("ready."));
  }

  oled.fillRect(0, 60, 127, 4, BLACK); // clear progress bar
  oled.display();    

}

FrontPanelControls.ino

C/C++
functions for the OLED display, rotary knob, LEDs and buttons.
/*
Volumetric Display Project
front panel elements : OLED display, rotary knob and buttons
Maker Mac 
June 2023
*/
 


/* --------------------------------------------------------------------------------------
 Called from main() -> check user Interface (buttons, rotary encoder on front panel)
-------------------------------------------------------------------------------------- */
void user_input()
{
  int32_t tmp0_position, tmp_position, t0b, t1b;

  // check buttons 
  if (digitalRead(Button0) == 0)  // ON/OFF button
  {
    Serial.println(F("ON/OFF button pressed."));
    if (projector_on) 
      TurnProjectorOff();
    else
      TurnProjectorOn();
  }

  if (digitalRead(Button1) == 0)  // WAVE button
  {
     Serial.println(F("WAVE button pressed."));
     if (!output_enabled)   // if not enabled, turn on
       WaveON();
     else                       
       WaveOFF();           // turn off 

  }

  if (digitalRead(Button2) == 0)  // SYNC button
  {

    t0b=micros();
    while(digitalRead(Button2) == 0) // wait until released
    {
      t1b=micros()-t0b;
      if (t1b>300000 && proj_sync_mode) analogWrite(LEDbtn2, 0);  // SYNC button off  
      if (t1b>300000 && !proj_sync_mode) analogWrite(LEDbtn2, LEDon);  // SYNC button on  
    }
//    Serial.print("tb=");     Serial.println(t1b);
    if (t1b>300000) // evaluate press duration
    {
     Serial.println(F("SYNC button LONG pressed."));

     if (!proj_sync_mode)     // if not in proj sync mode
      {  
         proj_sync_mode=true;  // set sync to on
         Serial.println("Mode 0 (SYNC) enabled");
         analogWrite(LEDbtn2, LEDon);  // light up SYNC button LED
      }
      else                     // else enter free running mode
      {
         proj_sync_mode=false; 
         wavesample=0;
         TCCR4B = (1 << CS40) | (1 << WGM42);   // start Timer4   
         Serial.println("Mode 1 (free running) enabled");
         analogWrite(LEDbtn2, 0);  // SYNC button off         
      }


    }else
    {
     analogWrite(LEDbtn2, 0);  // SYNC button off         
     Serial.println(F("SYNC button short pressed."));
     Serial.println(F("\n--- skipping one wheel sync pulse"));
     divcount--;
     analogWrite(LEDbtn2, LEDon);  // light up SYNC button LED
    }





  }


  // handle rotary encoder 
  int32_t new_position = ss.getEncoderPosition();  // get actual encoder position
  if (encoder_position != new_position)    // encoder changed ?
  {
    sspixel.setPixelColor(0, 255,255,0);  // yes, set Neopixel to yellow
    sspixel.show();
    
    if (new_position < encoder_position)  // increment menu pos
    {
      menu_position++;
      if (menu_position > menusize) menu_position=0;
    }
    if (new_position > encoder_position) // decrement menu pos
    {
      menu_position--;
      if (menu_position < 0) menu_position=menusize;
    }
    showmenu();      // show updated menu  
    encoder_position = new_position;      // save position 

  }  

  // check encoder button
  if (! ss.digitalRead(SS_SWITCH))         // Encoder button pressed ?
  {
    sspixel.setPixelColor(0, 0,255,255);  // Yes, set Neopixel to cyan
    sspixel.show();

    Serial.print("enter menu "); Serial.println(menu_position);

    while (! ss.digitalRead(SS_SWITCH));  // wait until button is released

    switch(menu_position)    // activate selected menu iten
    {  
      case 0:               // Show Status information
        showstatus();
        menu_timeout = 0;  // prevent to show main page
       break;
      case 1:               // Set Freq
        adjustfreq();                   
       break;
      case 2:               // Set Phase
        adjustphase();
       break;
      case 3:               // LV open pos
        adjustLVopen();    
       break;
      case 4:               // LV close pos
        adjustLVclose();
       break;
      case 5:  // (re-)set SBS mode
        oled.clearDisplay(); 
        oled.setTextSize(2); 
        oled.setCursor(0, 10);        
        oled.println("Set Mode:");
        oled.setCursor(0, 30);        
        oled.println("SBS");
        oled.setTextColor(WHITE, BLACK);   
        oled.display();                
        Serial.println(F("\nSetting 3D Mode to Side-by-side"));
        projcommand="*3d=sbs#";
        DoProjectorCommand();
        Serial.println("Response='"+projresponse+"'");
       break;
      case 6:  // Switch Input
        if (proj_input == 1)
        {
          oled.clearDisplay(); 
          oled.setTextSize(2); 
          oled.setCursor(0, 10);        
          oled.println("Set Input:");
          oled.setCursor(0, 30);        
          oled.println("int(HDMI1)");
          oled.setTextColor(WHITE, BLACK);   
          oled.display();                    
          Serial.println(F("\nSet proj input to HDMI"));
          projcommand="*sour=hdmi#";
          DoProjectorCommand();
          Serial.println("Response='"+projresponse+"'");
          proj_input=2;
        } else
       {
          oled.clearDisplay(); 
          oled.setTextSize(2); 
          oled.setCursor(0, 10);        
          oled.println("Set Input:");
          oled.setCursor(0, 30);        
          oled.println("ext(RGB)");
          oled.setTextColor(WHITE, BLACK);   
          oled.display();                    
          Serial.println(F("\nSet proj input to RGB(PC)"));
          projcommand="*sour=RGB#";
          DoProjectorCommand();
          Serial.println("Response='"+projresponse+"'");
          proj_input=1;
        }
       break;
      default:
       Serial.println("unknown menu option");
      break;
      
    }
  }
}

// Menu Status : show some status information
void showstatus()
{
        oled.clearDisplay(); // clear display
        oled.setTextSize(1);          
        oled.setTextColor(WHITE, BLACK); 
        oled.setCursor(0, 10);        
        oled.println("        STATUS : ");
        oled.setCursor(0, 20);        
        oled.print("Proj fw = "); oled.print(fps_proj); oled.println(" Hz  ");             
        oled.setCursor(0, 30);        
        oled.print("Phase   = "); oled.println(wavetable_start);             
        oled.setCursor(0, 40);        
        oled.print("LV open = "); oled.println(valves_disable);             
        oled.setCursor(0, 50);        
        oled.print("LV close= "); oled.println(valves_enable);             
        oled.setCursor(95, 55);        
        oled.println("(1/1)");
        oled.display();          
}

// Menu Freq : adjust frequency (6-50Hz) using encoder knob
void adjustfreq()
{
        oled.clearDisplay(); 
        oled.setTextSize(2); 
        oled.setCursor(0, 10);        
        oled.println("Set Freq:");
        oled.setTextColor(WHITE, BLACK);   
        oled.display();          
        
        int32_t tmp0_position = ss.getEncoderPosition();  // get actual encoder position
        int freq_old=freq;
        while (ss.digitalRead(SS_SWITCH))  // adjust until button is pressed
        {
          oled.setCursor(0, 30);        
          oled.print(freq); oled.println(" Hz  ");
          oled.display();          

          int32_t tmp_position = ss.getEncoderPosition();
          if (tmp0_position > tmp_position) freq++;
          if (tmp0_position < tmp_position) freq--;
          if (freq<6) freq=6;
          if (freq>50) freq=50;
          if (freq_old != freq)
          {
            fs = freq*wavetable_entries ; // output freq * 256 steps of wavetable give sample freq fs
            Ts = 1000000/fs;              // sample-time Ts=1/fs  default is 130us for 30Hz*256 (values in us)
            SetTimer4((int)Ts);           // Set Timer4 to  .. us
            Serial.print(" freq  =");
            Serial.print(freq);
            Serial.print(" -> Ts  =");
            Serial.print(Ts);
            Serial.println(F(" us "));

          }
          freq_old=freq;
          tmp0_position=tmp_position;
           
        }
        while (! ss.digitalRead(SS_SWITCH));  // wait until button is released
 
}

// Menu Phase : adjusts phase (wavetable_start 0-255) using Encoder knob
void adjustphase()
{

        oled.clearDisplay(); 
        oled.setTextSize(2); 
        oled.setCursor(0, 10);        
        oled.println("Set Phase:");
        oled.setTextColor(WHITE, BLACK);   
        oled.display();          
        int32_t tmp0_position = ss.getEncoderPosition();  // get actual encoder position
        int start_old=wavetable_start;
        int pha = start_old;
        while (ss.digitalRead(SS_SWITCH))  // adjust until button is pressed
        {
          oled.setCursor(0, 30);        
          oled.print(wavetable_start); oled.println("   ");
          oled.display();          

          int32_t tmp_position = ss.getEncoderPosition();
          if (tmp0_position > tmp_position) pha++;
          if (tmp0_position < tmp_position) pha--;
          if (pha<0) pha=255;
          if (pha>255) pha=0;
          if (start_old != pha)
          {
              Serial.print(F("\n--- shift wavetable start to "));
              Serial.print(pha);
              SetPhase(pha);
              Serial.println(F(" ...ok"));
          }
          start_old=pha;
          tmp0_position=tmp_position;
        }
        while (! ss.digitalRead(SS_SWITCH));  // wait until button is released

}

// Menu LVopen : adjusts lightvalve open position (0-64) using Encoder knob
void adjustLVopen()
{
        oled.clearDisplay(); 
        oled.setTextSize(2); 
        oled.setCursor(0, 10);        
        oled.println("Set LV open:");
        oled.setTextColor(WHITE, BLACK);   
        oled.display();          
        int32_t tmp0_position = ss.getEncoderPosition();  // get actual encoder position
        int valves_old=valves_disable;
        while (ss.digitalRead(SS_SWITCH))  // adjust until button is pressed
        {
          oled.setCursor(0, 30);        
          oled.print(valves_disable); oled.println("   ");
          oled.display();          

          int32_t tmp_position = ss.getEncoderPosition();
          if (tmp0_position > tmp_position) valves_disable++;
          if (tmp0_position < tmp_position) valves_disable--;
          if (valves_disable<0) valves_disable=0;
          if (valves_disable>63) valves_disable=63;
          if (valves_old != valves_disable)
          {
              Serial.print(F("\n--- set valves disable = "));
              Serial.print(valves_disable);
          }
          valves_old=valves_disable;
          tmp0_position=tmp_position;
        }
        while (! ss.digitalRead(SS_SWITCH));  // wait until button is released

}

// Menu LVclose : adjusts lightvalve block position (0-64) using Encoder knob
void adjustLVclose()
{
        oled.clearDisplay(); 
        oled.setTextSize(2); 
        oled.setCursor(0, 10);        
        oled.println("Set LV close:");
        oled.setTextColor(WHITE, BLACK);   
        oled.display();          
        int32_t tmp0_position = ss.getEncoderPosition();  // get actual encoder position
        int valves_old=valves_enable;
        while (ss.digitalRead(SS_SWITCH))  // adjust until button is pressed
        {
          oled.setCursor(0, 30);        
          oled.print(valves_enable); oled.println("   ");
          oled.display();          

          int32_t tmp_position = ss.getEncoderPosition();
          if (tmp0_position > tmp_position) valves_enable++;
          if (tmp0_position < tmp_position) valves_enable--;
          if (valves_disable<0) valves_enable=0;
          if (valves_disable>63) valves_enable=63;
          if (valves_old != valves_enable)
          {
              Serial.print(F("\n--- set valves enable = "));
              Serial.print(valves_enable);
          }
          valves_old=valves_enable;
          tmp0_position=tmp_position;
        }
        while (! ss.digitalRead(SS_SWITCH));  // wait until button is released

}


// show menu as rotating bar on the OLED display
void showmenu()
{
  oled.clearDisplay(); // clear display

  // show previous menu entry
  signed int l=menu_position-1;
  if (l<0) l=menusize;
  oled.setTextSize(2);          // text size
  oled.setTextColor(WHITE, BLACK);     // text color (normal)
  oled.setCursor(0, 10);        // position to display
  oled.println(menuitem[l]); // text to display

  // show currently active menu entry
  l++;
  if (l>menusize) l=0;
  oled.setTextColor(BLACK, WHITE);     // text color (highlighted)
  oled.setCursor(0, 25);        // position to display
  oled.println(menuitem[l]); // text to display

  // show next menu entry
  l++;
  if (l>menusize) l=0;
  oled.setTextColor(WHITE, BLACK);     // text color (normal)
  oled.setCursor(0, 40);        // position to display
  oled.println(menuitem[l]); // text to display
 
  oled.display();   // show on OLED

  menu_timeout = MENUTIME;  // restart menu time-out timer 
  
}

// Show main display on OLED (if menu is turned off)
void show_main_page()
{
  oled.clearDisplay(); // clear display
  drawbitmap();  // show rebel bitmap

  oled.setTextSize(1);          
  oled.setTextColor(WHITE, BLACK); 
  oled.setCursor(0, 53);        
  oled.println("        READY");
  oled.display();   
  
  sspixel.setPixelColor(0, 0,0,0);  // switch off Neopixel
  sspixel.show();

}

/* --------------------------------------------------------------------------------------
 draws a bitmap on the OLED display (the "rebel" logo) 
---------------------------------------------------------------------------------------- */
void drawbitmap(void) 
{
  oled.clearDisplay();
  oled.drawBitmap(
    (oled.width()  - LOGO_WIDTH ) / 2,
    (oled.height() - LOGO_HEIGHT) / 2,
    rebel_logo, LOGO_WIDTH, LOGO_HEIGHT, 1);
  oled.display();
} 

sinus.h

C/C++
wave table for sine wave (256 entries)
// Wave table for one sine wave cycle 
static  int  waveform[]  ={  
0    , 
0    , 
0    , 
0    , 
0    , 
0    , 
1    , 
1    , 
2    , 
2    , 
3    , 
3    , 
4    , 
5    , 
6    , 
7    , 
8    , 
9    , 
10    , 
11    , 
13    , 
14    , 
16    , 
17    , 
19    , 
20    , 
22    , 
24    , 
26    , 
28    , 
30    , 
32    , 
34    , 
36    , 
38    , 
40    , 
43    , 
45    , 
47    , 
50    , 
52    , 
55    , 
57    , 
60    , 
63    , 
65    , 
68    , 
71    , 
74    , 
77    , 
79    , 
82    , 
85    , 
88    , 
91    , 
94    , 
97    , 
100    , 
103    , 
106    , 
110    , 
113    , 
116    , 
119    , 
122    , 
125    , 
128    , 
131    , 
134    , 
138    , 
141    , 
144    , 
147    , 
150    , 
153    , 
156    , 
159    , 
162    , 
165    , 
168    , 
171    , 
174    , 
177    , 
180    , 
182    , 
185    , 
188    , 
191    , 
193    , 
196    , 
199    , 
201    , 
204    , 
206    , 
208    , 
211    , 
213    , 
215    , 
218    , 
220    , 
222    , 
224    , 
226    , 
228    , 
230    , 
232    , 
233    , 
235    , 
237    , 
238    , 
240    , 
241    , 
242    , 
244    , 
245    , 
246    , 
247    , 
248    , 
249    , 
250    , 
250    , 
251    , 
252    , 
252    , 
253    , 
253    , 
253    , 
254    , 
254    , 
254    , 
254    , 
254    , 
254    , 
254    , 
253    , 
253    , 
252    , 
252    , 
251    , 
251    , 
250    , 
249    , 
248    , 
247    , 
246    , 
245    , 
244    , 
243    , 
241    , 
240    , 
238    , 
237    , 
235    , 
234    , 
232    , 
230    , 
228    , 
226    , 
224    , 
222    , 
220    , 
218    , 
216    , 
214    , 
211    , 
209    , 
207    , 
204    , 
202    , 
199    , 
197    , 
194    , 
191    , 
189    , 
186    , 
183    , 
180    , 
177    , 
175    , 
172    , 
169    , 
166    , 
163    , 
160    , 
157    , 
154    , 
151    , 
148    , 
144    , 
141    , 
138    , 
135    , 
132    , 
129    , 
126    , 
123    , 
120    , 
116    , 
113    , 
110    , 
107    , 
104    , 
101    , 
98    , 
95    , 
92    , 
89    , 
86    , 
83    , 
80    , 
77    , 
74    , 
72    , 
69    , 
66    , 
63    , 
61    , 
58    , 
55    , 
53    , 
50    , 
48    , 
46    , 
43    , 
41    , 
39    , 
36    , 
34    , 
32    , 
30    , 
28    , 
26    , 
24    , 
22    , 
21    , 
19    , 
17    , 
16    , 
14    , 
13    , 
12    , 
10    , 
9    , 
8    , 
7    , 
6    , 
5    , 
4    , 
4    , 
3    , 
2    , 
2    , 
1    , 
1    , 
1    , 
0    
};

rebel_logo.h

C/C++
picture of rebel logo (128x64 pixels) to show on OLED
// 'rebel_logo3', 128x64px
const unsigned char rebel_logo [] PROGMEM = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x81, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0xe0, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x07, 0xe0, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x0f, 0xf0, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x1f, 0xf8, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x1f, 0xf8, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x0f, 0xf8, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xc0, 0xef, 0xf3, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x81, 0xef, 0xf7, 0x81, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x81, 0xff, 0xff, 0xc1, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x01, 0xff, 0xff, 0x80, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0xff, 0xff, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x7f, 0xfe, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x3f, 0xfc, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x1f, 0xf8, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x0f, 0xf8, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x0f, 0xf0, 0x00, 0x3f, 0x80, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x00, 0x07, 0xf0, 0x00, 0x3f, 0x80, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x00, 0x07, 0xe0, 0x00, 0x3f, 0x80, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x00, 0x07, 0xe0, 0x00, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x00, 0x07, 0xe0, 0x00, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0x00, 0x07, 0xe0, 0x00, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0x00, 0x07, 0xe0, 0x00, 0x7f, 0xc0, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0x00, 0x07, 0xe0, 0x00, 0x7f, 0xc0, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x07, 0xe0, 0x00, 0x7f, 0xc0, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x07, 0xf0, 0x00, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x0f, 0xf0, 0x00, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0x80, 0x1f, 0xf8, 0x01, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xc0, 0x1f, 0xfc, 0x03, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xe0, 0x7f, 0xfe, 0x07, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

Tools for the "DIY Volumetric Display" project

Assembly x86
This is a set of tools for the "DIY Volumetric Display" project:
These software tools are made for this project exclusively. They run only on Windows (any recent version is ok). Important : They are not fully tested and may not be perfect ! So use them at your own risk.
No installation is required, just copy the entire contents of the zip archive into a directory.
1. VolDispSlicer.exe
2. VolDisPlayer.exe
3. VolDisLayerConfig.exe
more information on how to use the tools is included in the archive
No preview (download only).

Credits

mac70
6 projects • 36 followers

Comments