RGB color switching based on motor RPM

Hi all! Its my first post so please forgive me any mistakes.

Ive been playing with Arduinos since a year now in my spare time, Ive learned some basics by copying and merging some sketches found on the the internet. Now I am kind of able to write my own stuff, still basic though.

Ive done some small projects, and now Im working on a RGB laser spirograph, and I’m stuck. Here’s the concept:

I am shooting an RGB laser on to a single motor (to begin with), with a fitted mirror at a little angle, projecting a circle (see attached pic).

The basic idea is to be able to split the circle in to a dfferent color sections, starting with basic 7 (Red, Green, Blue, Cyan, Magenta, Yellow and White) and the last section to be black (all lasers off).

I have fitted a 3144 hall effect sensor and a little magnet on the mirror to detect the revolution and measure RPM.

I am using arduino nano.

The laser is a cheap chinese 500mw RGB setup with TTL which is working fine when hooked up to digital outputs.

The motor is currently powered from an external regulated supply, to be implemented as PWM later.

So I figured three ways of making it work:

  1. Measure the RPM or RPS, divide by the measuring time and then divide it by 8 and use the result as the delay time between switching colors.

  2. Reset a loop everytime the hall sensor gets high.

  3. Measure the time of one revolution, divide it by 8 and use the result as the delay time between switching colors.

I tried first two ways but failed totally as I am not that good with loops and the RPM/RPS did not work properly.

So I am currently working on the third way, and got some results, but only with low RPM, making the whole thing flicker and shake, and the idea is to keep it stable, regardless of the motor speed.

So here is my question - am I doing something wrong here? How to optimize the code?

I am sure what I want to achieve is probably one of the easiest thigs to do with arduino but I’m just not that good with it yet, so any other suggestions are welcome.

The code is a mixture of some other codes I found with my own additions.

Any help would be appreciated, thanks, Greg.

int Hallpin = 3;
int Redpin = 9;
int Greenpin = 10;
int Bluepin = 11;

unsigned long StartTime;
unsigned long EndTime;
unsigned long Duration;
unsigned long One8th;
byte TimerRunning;

void setup(){
pinMode (Hallpin, INPUT_PULLUP);
pinMode (Redpin, OUTPUT);
pinMode (Greenpin, OUTPUT);
pinMode (Bluepin, OUTPUT);
Serial.begin(9600);
 
}
void loop(){
  if (TimerRunning == 0 && digitalRead(Hallpin) == LOW){ // button pressed & timer not running already
  StartTime = micros();
 TimerRunning = 1;
  }
 if (TimerRunning == 1 && digitalRead(Hallpin) == HIGH){ // timer running, button released
 EndTime = micros();
 TimerRunning = 0;
 Duration = EndTime - StartTime;
   One8th = Duration / 8 * 10;

  Serial.print ("Revolution time in microseconds: ");
  Serial.println (Duration);
  Serial.println (One8th);

   // color ring

  // RED
  digitalWrite(Redpin, HIGH);
  delayMicroseconds(One8th);
  digitalWrite(Redpin, LOW);
  //GREEN
  digitalWrite(Greenpin, HIGH);
  delayMicroseconds(One8th);
  digitalWrite(Greenpin, LOW);
  //BLUE
  digitalWrite(Bluepin, HIGH);
  delayMicroseconds(One8th);
  digitalWrite(Bluepin, LOW);
  //CYAN
  digitalWrite(Greenpin, HIGH);
  digitalWrite(Bluepin, HIGH);
  delayMicroseconds(One8th);
  digitalWrite(Greenpin, LOW);
  digitalWrite(Bluepin, LOW);
  //MAGENTA
  digitalWrite(Redpin, HIGH);
  digitalWrite(Bluepin, HIGH);
  delayMicroseconds(One8th);
  digitalWrite(Redpin, LOW);
  digitalWrite(Bluepin, LOW);
  //YELLOW
  digitalWrite(Redpin, HIGH);
  digitalWrite(Greenpin, HIGH);
  delayMicroseconds(One8th);
  digitalWrite(Redpin, LOW);
  digitalWrite(Greenpin, LOW);
  //WHITE
  digitalWrite(Redpin, HIGH);
  digitalWrite(Greenpin, HIGH);
  digitalWrite(Bluepin, HIGH);
  delayMicroseconds(One8th);
  digitalWrite(Redpin, LOW);
  digitalWrite(Greenpin, LOW);
  digitalWrite(Bluepin, LOW);
  //BLACK
  digitalWrite(Redpin, LOW);
  digitalWrite(Greenpin, LOW);
  digitalWrite(Bluepin, LOW);
  delayMicroseconds(One8th);
  digitalWrite(Redpin, LOW);
  digitalWrite(Greenpin, LOW);
  digitalWrite(Bluepin, LOW);
    }
}

I got board, try something like this..

int color;
unsigned long lastTime;
unsigned long deltaTime;
unsigned long nextTime;


void setup(void) {

   pinMode (Hallpin, INPUT_PULLUP);
   pinMode (Redpin, OUTPUT);
   pinMode (Greenpin, OUTPUT);
   pinMode (Bluepin, OUTPUT);

   digitalWrite(Redpin, LOW);
   digitalWrite(Greenpin, LOW);
   digitalWrite(Bluepin, LOW);

   lastTime = micros();
}


resetColor() {

   color = 0;
   stepColor();
}


void stepColor(void) {
   
   digitalWrite(Redpin, LOW);
   digitalWrite(Greenpin, LOW);
   digitalWrite(Bluepin, LOW);
   color++;
   switch(color) {
      case 1 : digitalWrite(Redpin, HIGH);                                                                                     break;
      case 2 : digitalWrite(Greenpin, HIGH);                                                                                  break;
      case 3 : digitalWrite(Bluepin, HIGH);                                                                                     break;
      case 4 : digitalWrite(Greenpin, HIGH); digitalWrite(Bluepin, HIGH);                                          break;
      case 5 : digitalWrite(Redpin, HIGH); digitalWrite(Bluepin, HIGH);                                             break;
      case 6 : digitalWrite(Redpin, HIGH); digitalWrite(Greenpin, HIGH);                                           break;
      case 7 : digitalWrite(Redpin, HIGH); digitalWrite(Greenpin, HIGH); digitalWrite(Bluepin, HIGH);  break;
      case 8 : break;
   }
}


void loop(void) {

   unsigned long now;

   if (digitalRead(Hallpin)) {
      now = micros();
      resetColor();
      deltaTime = (now - lastTime)/8;
      lastTime = now;
      nextTime = lastTime + deltaTime;
   } else if (micros()>nextTime) {
     stepColor();
     nextTime = nextTime + deltaTime;
   }
}

Its never been compiled. So it probably has plenty errors in it. But it may help you get a new way to look at it. And I know it doesn't take account or rollover. That can be fixed later if necessary.

-jim lee

consider

// sequence LEDs based on Hall sensor

#if 0
# define Hallpin     3
# define Redpin      9
# define Greenpin    10
# define Bluepin     11

#else
# define Hallpin     A1
# define Redpin      12
# define Greenpin    10
# define Bluepin     11
#endif

unsigned long usec;
unsigned long usecCyc = 0;

// -----------------------------------------------------------------------------
void setup (void)
{
    Serial.begin (9600);

    pinMode (Hallpin,   INPUT_PULLUP);
    pinMode (Redpin,    OUTPUT);
    pinMode (Greenpin,  OUTPUT);
    pinMode (Bluepin,   OUTPUT);
}

// -----------------------------------------------------------------------------
struct Seq {
    byte    red;
    byte    blue;
    byte    green;
};

Seq seqs [] = {
#if 1
    { 1, 0, 0 },
    { 0, 0, 1 },
    { 0, 1, 0 },
    { 0, 1, 1 },

    { 1, 1, 0 },
    { 1, 0, 1 },
    { 1, 1, 1 },
    { 0, 0, 0 },
#else
    { 1, 0, 0 },
    { 0, 0, 1 },
    { 0, 1, 0 },
#endif
};

#define N_SEQ  (sizeof(seqs)/sizeof(Seq))

// -------------------------------------
void
sequencer (void)
{
    static unsigned long usecLst = 0;
    static unsigned      seqIdx  = 0;

    if (usec - usecLst < (usecCyc / N_SEQ))
        return;

    usecLst = usec;

    Seq  *p = & seqs [seqIdx];

    digitalWrite (Redpin,   ! p->red);
    digitalWrite (Bluepin,  ! p->blue);
    digitalWrite (Greenpin, ! p->green);

    seqIdx = seqIdx < (N_SEQ-1) ? seqIdx +1 : 0;
}

// -----------------------------------------------------------------------------
void
loop (void)
{
    static unsigned long usecLst = 0;

    usec = micros ();

    static byte hallLst = 0;
           byte hall    = digitalRead (Hallpin);

    if (hallLst != hall)  {
        hallLst = hall;

        if (hall)  {
            usecCyc = usec - usecLst;
            usecLst = usec;
        }
    }
    
    sequencer ();
}
  if (TimerRunning == 0 && digitalRead(Hallpin) == LOW){ // button pressed & timer not running already
  StartTime = micros();
 TimerRunning = 1;
  }
 if (TimerRunning == 1 && digitalRead(Hallpin) == HIGH){ // timer running, button released
 EndTime = micros();
 TimerRunning = 0;
 Duration = EndTime - StartTime;

Part of the problem here is that you are measuring the time that 'Hallpin' is LOW. This leaves out the amount 'Hallpin' is HIGH which might be a significant part of the revolution.

One8th = Duration / 8 * 10;

It's not clear why you multiply by 10. Wouldn't that make each interval 10/8th of a revolution?

Here is my first guess as to what might work. If looks for the Hallpin going from LOW to HIGH (you can change that to look for HIGH to LOW if that positions the pattern better). From that edge it can calculate the time since the last similar edge and thus the time of last revolution. Dividing by 8 gets the length of each color interval. Knowing when the revolution started we can calculate which of the 8 intervals we are currently in and set the color to match.

// Red, Green, Blue, Blue-Green, Magenta, Yellow, White, Black
const int RedValues[] = {HIGH, LOW, LOW, LOW, HIGH, HIGH, HIGH, LOW};
const int GreenValues[] = {LOW, HIGH, LOW, HIGH, LOW, HIGH, HIGH, LOW};
const int BlueValues[] = {LOW, LOW, HIGH, HIGH, HIGH, LOW, HIGH, LOW};


void loop()
{
  unsigned long currentTime = micros();
  static unsigned long previousTime = 0;


  int currentState = digitalRead(Hallpin);
  static int previousState = LOW;


  if (currentState != previousState)  // State Change Detected
  {
    previousState = currentState;
    if (currentState == LOW)
    {
      Duration = currentTime - previousTime;
      One8th = Duration / 8;


      previousTime = currentTime;
    }
  }


  // Which color interval are we in?
  unsigned whichInterval = ((currentTime - previousTime) / One8th) % 8;


  digitalWrite(Redpin, RedValues[whichInterval]);
  digitalWrite(Greenpin, GreenValues[whichInterval]);
  digitalWrite(Bluepin, BlueValues[whichInterval]);
}

Thank you for all the replies!

jimLee's and gcjr's codes did not work, as the first one only blinks the red laser for around one second, and the second one blinks all colours very fast, even with the motor off, but It could be my fault as I had to complete these codes and there's a big chance i did it wrong. But big thanks for trying.

As for johnwasser's post - you Sir are a star, it works perfectly even at around 5000RPM which is twice what I wanted it to work at. There is a slight shake every now and then but I can live with that.

Now I have to analyze the code and try to add different color patterns, and another motor with another hall sensor. If I fail I will come back here :) .

Big thanks again, I'll post the final effect here when I get there. Greg.

|500x445 |500x242

Damn! I feel so humbled!

Good on ya! for getting it going!

-jim lee

gregsmif: Quote from: gregsmifNow I have to analyze the code and try to add different color patterns, and another motor with another hall sensor. If I fail I will come back here :) .

I would start with a list of the eight possible colors. Let's say we store all three flags (Red, Green, Blue) in the bottom 3 bits of an integer. The 8 patterns in numeric order would be:

enum ColorName {BLACK, BLUE, GREEN, CYAN, RED, MAGENTA, YELLOW, WHITE};

(An 'enum' is a quick way of assigning names to consecutive integers, by default starting with zero.)

Then you can easily make as many patterns as you want:

const ColorName Rainbow[] = {RED, YELLOW, GREEN, CYAN, BLUE, MAGENTA};
const byte RainbowLength = sizeof Rainbow / sizeof Rainbow[0];

To set the active pattern you would store the pointer and the length in global variables.

ColorName * PatternColors = Rainbow;
byte PatternLength = RainbowLength;

Then instead of dividing by 8 you would divide by PatternLength and use the value you find in the Pattern array to set the LEDs:

  ColorName color = Pattern[whichInterval];
  digitalWrite(Redpin,    (color & B100) != 0);
  digitalWrite(Greenpin,  (color & B010) != 0);
  digitalWrite(Bluepin,   (color & B001) != 0);