Lucid dreaming glasses - first project

These are basically safety glasses from Home Depot with an RGB LED in each lens. The program runs in two phases. First, for half an hour, it will slowly brighten and dim the lights to random colors. This is meant to simulate hypnagogic imagery (the shapes and colors you see as you're falling asleep). The brightening and dimming gets progressively faster and the pause in between gets shorter as time goes on.

After the half hour has passed (hopefully you are asleep by this point) the second phase begins. In this phase the red LEDs flash for ten seconds every ten minutes. This is a signal to your dreaming self that you are in fact dreaming and free to do whatever you please.

(The photos are from before i got RGB LEDs, I was just using blue then)

They work pretty well but are a little bit uncomfortable. I wake up often and find that they are no longer on my face, which is strange because the strap seems tight enough that they shouldn't just fall off. I think I might be taking them off with my hands in my sleep.

Code follows. First is a task scheduler I wrote to schedule the lightening.

#ifndef TASK_SCHEDULER
#define TASK_SCHEDULER

void setupTaskScheduler(int _maxTasks, int _tickIntervalMs);
void scheduleTimer5Task(void (*action)(), int delayMs);
void startTicking();

#endif
const int prescaler = 1024; // How many clock cycles it takes before the underlying timer (Timer1) increments
const int timerHertz = 16000000 / prescaler; // How many times per second the underlying timer (Timer1) increments. 16,000,000 is the Arduino's clock speed

int maxTasks;
unsigned long tickIntervalMs;
int compareMatchRegister;

struct Task
{
	void (*action)(void*);
        void* argument;
	unsigned long delayTicks;
};

struct Task** tasks;

void setupTaskScheduler(int _maxTasks, unsigned long _tickIntervalMs)
{
        if (tasks != NULL)
	{
            for (int i = 0; i < maxTasks; i++)
            {
               Task* task = tasks[i];
               free(task); 
            }
            
            free(tasks);
	}

        tickIntervalMs = _tickIntervalMs;
        double interruptHertz = 1000 / _tickIntervalMs; // interruptHertz = How many times per second the caller is asking to check for and execute due tasks
	maxTasks = _maxTasks;
	tasks = (Task**)calloc(maxTasks, sizeof(Task*));

        compareMatchRegister = timerHertz / interruptHertz; // compareMatchRegister = when the underlying timer (Timer1) hits this value, it interrupts and then resets to zero
}

int scheduleTimer1Task(void (*action)(void*), void* argument, unsigned long delayMs)
{
  for (int i = 0; i < maxTasks; i++)
  {
    if (tasks[i] == NULL)
    {
      Task* ptr = (Task*)malloc(sizeof(struct Task));
      ptr->action = action;
      ptr->argument = argument;
      ptr->delayTicks = delayMs / tickIntervalMs;
      tasks[i] = ptr;
      return true; // Found an empty spot in the task list for this task
    }
  }
  
  return false; // No spot for this task found, scheduling request rejected
}

void startSchedulerTicking()
{
  noInterrupts();
  
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 1hz increments
  OCR1A = compareMatchRegister;// = (16*10^6) / (1*1024) - 1 (must be <65536)
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS10 and CS12 bits for 1024 prescaler
  TCCR1B |= (1 << CS12) | (1 << CS10);  
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);
  
  interrupts();
}

// This is the timer interrupt
ISR( TIMER1_COMPA_vect )
{
  for (int i = 0; i < maxTasks; i++)
  {
    if (tasks[i] != NULL)
    {
      Task* task = tasks[i];
      unsigned long ticks = task->delayTicks;
      
      if (ticks == 0)
      {
         void (*action)(void*) = task->action;
         void* argument = task->argument;
         free(task);
         tasks[i] = NULL;
         action(argument);
       }
      else
      {
        task->delayTicks--;
      }
    } 
  }
}

And the project-specific code:

int scheduleTimer1Task(void (*action)(void*), void* argument, unsigned long delayMs); // Don't know why putting this in a header file and including it doesn't work but I'll deal

const unsigned long DelayMs = 30;
const int AnalogOutMaxForHypnaMode = 30;
const int MaxFadeMs = 2000;
const int MinFadeMs = 200;
const unsigned long RampUpTimeMs = 1800000; // 30 minutes
const unsigned long RampUpStepIntervalMs = 10000; // 10 seconds
const unsigned int NumberOfLeds = 2;
const unsigned int NumberOfColorsPerLed = 3;

const unsigned long LdModeInitialDelayMs = 1200000; // 60 minutes
const unsigned long LdModeDelayBetweenPulsesMs = 600000; // 10 minutes
const unsigned long LdModePulseDurationMs = 10000; // 10 seconds

const int LedPins[NumberOfLeds][NumberOfColorsPerLed] = 
{
  { 2, 3, 4 },
  { 5, 6, 7 },
};

double ledIncrements[NumberOfLeds][NumberOfColorsPerLed] = 
{
  { 0, 0, 0 },
  { 0, 0, 0 }, 
};

double ledTargets[NumberOfLeds][NumberOfColorsPerLed] = 
{
  { 0, 0, 0 },
  { 0, 0, 0 }, 
};

double ledCurrentVoltages[NumberOfLeds][NumberOfColorsPerLed] = 
{
  { 0, 0, 0 },
  { 0, 0, 0 },  
};

int fadeMs = MaxFadeMs;
int maxOffTimeMs = fadeMs * 5;
unsigned long nextLedUpdateTime = 0;

void setup()
{
  randomSeed(analogRead(0));
  setupTaskScheduler(NumberOfLeds * NumberOfColorsPerLed + 1, DelayMs);
  startSchedulerTicking();
  scheduleTimer1Task(&rampUp, NULL, RampUpStepIntervalMs);
  
  for (int led = 0; led < NumberOfLeds; led++)
  {
    for (int color = 0; color < NumberOfColorsPerLed; color++)
    {
      pinMode(LedPins[led][color], OUTPUT);
    }
    
    scheduleLed(led);
  }
}

void loop()
{
    unsigned long mill = millis();
    
    if (mill > LdModeInitialDelayMs)
    {
      ldMode();
    }
  
    if (mill > nextLedUpdateTime)  
    {
      for (int led = 0; led < NumberOfLeds; led++)
      {
         if (ledDo(led))
         {
           scheduleLed(led);
         } 
      }
    
      nextLedUpdateTime = millis() + DelayMs;
    }
}

void ldMode()
{
  for (int led = 0; led < NumberOfLeds; led++)
  {
    for (int color = 0; color < NumberOfColorsPerLed; color++)
    {
      analogWrite(LedPins[led][color], LOW);
    }
  }
  
  while (true)
  {
    unsigned long startMillis = millis();
    
    while (millis() < startMillis + LdModePulseDurationMs)
    {
      analogWrite(2, AnalogOutMaxForHypnaMode); 
      analogWrite(6, AnalogOutMaxForHypnaMode);
      delay(500);
      analogWrite(2, LOW); 
      analogWrite(6, LOW);
      delay(500);
    }
    
    delay(LdModeDelayBetweenPulsesMs);
  }
}

void rampUp(void*)
{
  unsigned long mill = millis();
  
  if (mill > RampUpTimeMs)
  {
    return;
  }
  
  double multiplier = 1.0 - ((double)mill / (double)RampUpTimeMs);
  fadeMs = (multiplier * (MaxFadeMs - MinFadeMs)) + MinFadeMs;
  maxOffTimeMs = map(fadeMs, MinFadeMs, MaxFadeMs, 1000, maxOffTimeMs);
  scheduleTimer1Task(&rampUp, NULL, RampUpStepIntervalMs);
}

// Sets the LED's increments so that it begins moving toward its target
void updateLed(void* _led)
{
    int led = *(int*)_led;
    free(_led);
    
    double numberOfSteps = (double)fadeMs / (double)DelayMs;
    
    for (int color = 0; color < NumberOfColorsPerLed; color++)
    {
      ledIncrements[led][color] = ledTargets[led][color] / numberOfSteps;
    }
}

// Sets the LED's targets and schedules a call to updateLed to start its cycle
void scheduleLed(int led)
{
    unsigned long offTimeMs = random(0, maxOffTimeMs);
    
    for (int color = 0; color < NumberOfColorsPerLed; color++)
    {
      ledTargets[led][color] = random(1, AnalogOutMaxForHypnaMode);
    }
    
    void* ledVoid = malloc(sizeof(int));
    memcpy(ledVoid, &led, sizeof(int));
    scheduleTimer1Task(&updateLed, ledVoid, offTimeMs);
}

// Puts the LED back in its initial off state
void resetLed(int led)
{
  for (int color = 0; color < NumberOfColorsPerLed; color++)
  {
       analogWrite(LedPins[led][color], 0);
       ledCurrentVoltages[led][color] = 0;
       ledIncrements[led][color] = 0;
  } 
}

// Moves the led a step toward its final state
// Returns true if the led has reached its final state and returned to its initial state (the led has cycled completely from off to on to off again)
boolean ledDo(int led)
{
  boolean returnVal = false;
  
  for (int color = 0; color < NumberOfColorsPerLed; color++)
  {
    double increment = ledIncrements[led][color];
    
    int pin = LedPins[led][color];
    double output = ledCurrentVoltages[led][color] + increment;
    
    if (increment == 0)
    {
       continue; 
    }

    if (output < 0 || returnVal)
    {
       resetLed(led);
       return true;
    }
    else if (output > ledTargets[led][color])
    {
       ledIncrements[led][color] = -1 * increment;
    }
    else
    {
       analogWrite(pin, output);
       ledCurrentVoltages[led][color] = output;
    }
  }
  
  return false;
}

Oh!

I thought you might have included the EMG input to detect REM.

You need to use SMD LEDs wired with transformer wire (enamelled) to allow them to mount flat on the lens surface - on the inside would be handy and you can stick them down with silicone sealant. Transfer the connection to your stranded flex wire (get some "rainbow" cable and separate a "strap" with the required number of wires neatly together) on the side-piece of the glasses and run the cable with your strap around the back which holds the glasses on.

As you show it, I would hazard that you just caught the wiring with your hand whilst sleeping and pulled them off.

I wake up often and find that they are no longer on my face,

No wonder, if I had them on 10 second blinking, I would pull them off in my sleep.
When you can program different types of dreams, let us know.

Armor:
This is meant to simulate hypnagogic imagery (the shapes and colors you see as you're falling asleep).

Haven't ever seen these, is something wrong with me?

But I have seen warnings on LED datasheets and other publications from LED manufacturers to not look directly into them, especially blue, violet and white (which are really blue or violet with a phosphor) types.

Paul__B:
As you show it, I would hazard that you just caught the wiring with your hand whilst sleeping and pulled them off.

Could be. I would think though that if I'm just catching the wiring then the glue holding the LEDs in place would give before the pretty-secure-feeling headband (not in picture) slipped off. Maybe I'll just have to film myself sleeping :slight_smile:

I don't know but I doubt it. I don't think you need to go running to a doctor :slight_smile: I only see it occasionally, some people seem to see it all the time.

But I have seen warnings on LED datasheets and other publications from LED manufacturers to not look directly into them, especially blue, violet and white (which are really blue or violet with a phosphor) types.

Yes you should keep your eyes closed with something like this. And I would recommend not using UV LEDs :slight_smile:

Cool project, try it with a diffuser lens over each led, gives a warmer relaxed feeling.
Also these LEDs will allow you to use less wires to control them, no current limiting resistors needed and an SMD size.