Simple Rotary Encoder

Using just a small dc-motor and a resistor to make a crude rotary encoder for the Arduino.

I came up with a very simple  way to make rotary encoders. I needed a "turn the handle" interface for  an installation. Usually, this can be solved with either a potmeter or a  really fancy precision rotary encoder. But both have drawbacks. Potmeters need "end stops" to block overturning them.Fancy rotary encoders need high sampling rates, and take up several legs on the arduino each.

But: What spins and are good  at it: A DC motor. Reversing it and using it as a generator, you  suddenly have a simple way of knowing "Witch way are they turning the  wheel, and how fast".

You need

  • A small DC motor (permanent magnet type)
  • A resistor (10-20K)
  • An Arduino Board
Analog input value
No rotation The 3.3 V "flow" right through the windings of the DC motor 676
(1024/5*3.3)
Spin one way The DC motor acts as a generator, making maybe + 1 V (depending on the speed of your rotation). This is "added" to the + 3.3 V = 4.3 V 880
(> 676 : one way)
Spin other way DC motor makes - 1 V. This gets "subtracted" from the +3.3V = 2.3 V
471
(<676 : other way)

Considerations

  • ? Why not make 2.5 volt with a voltage divider and feed that to the DC motor, to get the zero-rotation to read 512 on the analog input?
  • ! Because you get bonus points for solving a problem with as few extra parts as possible.. :-)
  • ? Why the 15K resistor?
  • ! If the DC motor is spun really fast, the "overpower" to the Arduino Board makes it shut down. The resistor prevent this.

The code

This is the code from the Color & Sound installation. It uses the DC motors as encoders, and outputs DMX to control lights.

#include 
#include <DmxSimple.h>

/*
Christian Liljedahl, 2010. christian.liljedahl.dk christian@liljedahl.dk

Example of how to use 6 DC motors as rotary encoders for Arduino

This code returns a position on a circle (CirclePosition) from 0.0 to 1.0

If the DC motor is not rotating, the CirclePosition is not moving either
Once the DC motor is turned one way, CirclePosition starts moving up (or down). The speed depends on how fast the encoder (DC motor) is rotated.

Please note: CirclePosition is not the position of the axis of the DC motor. There is no way (to my knowledge) this setup can give you the position of the DC motor. 
Only: Are we turning clockwise or counterclockwise 
*/

// Arrays to keep readings
int AnalogInReading[6];
int AnalogZeroPoint[6];
double RotationSpeed[6];
double CirclePosition[6];

void setup() {
  // DMX stuff - This program controlls colors. 
  DmxSimple.maxChannel(30);
  //Serial
  Serial.begin(9600);

  // Make a reading
  readAllAnalog();
  // Set zeropoints
  calibrate();
  // Set starting point for cirle
  initPositions();  
}

long time = 0;

void loop() {
  // read the value from the sensor:
  readAllAnalog();
  calculateRotationSpeed();
  changeCirclePosition();

  //FictionalRotationValues();
  makeHueAndSendDmx();
}

void setDmxValues(byte rgb[], int startchannel){
  DmxSimple.write(startchannel, rgb[0]);
  startchannel++;
  DmxSimple.write(startchannel, rgb[1]);
  startchannel++;
  DmxSimple.write(startchannel, rgb[2]);
  startchannel++;
  DmxSimple.write(startchannel, 255);

}
void readAllAnalog(){
  for(int i=0;i<6;i++){
    AnalogInReading[i] = analogRead(i);
  }
}

void calibrate(){
  for(int i=0;i<6;i++){
    AnalogZeroPoint[i] = AnalogInReading[i];
  }
}

void initPositions(){
  for(int i=0;i<6;i++){
    CirclePosition[i] = 0.5;
  }
}

void calculateRotationSpeed(){
  for(int i=0;i<6;i++){
    RotationSpeed[i] = AnalogZeroPoint[i] - AnalogInReading[i];
  }  
}

void changeCirclePosition(){
  for(int i=0;i<6;i++){
    // What way do we rotate
    double rotFactor = (RotationSpeed[i]*RotationSpeed[i]-400)/1000000;
    if(RotationSpeed[i] > 20){

      // How fast do we rotate
      CirclePosition[i] += rotFactor;    
      // Wrap around
      if(CirclePosition[i] > 1.0) CirclePosition[i] = 0.0; 
    }
    if(RotationSpeed[i] < -20){
      CirclePosition[i] += -rotFactor;    
      // Wrap around
      if(CirclePosition[i] < 0.0) CirclePosition[i] = 1.0; 
    }
  } 
}

void makeHueAndSendDmx(){
  // Dmx start address of first fixture
  int channel = 1;

  for(int i=0;i<6;i++){
    byte colors[3];
    // We use the circlepositoin directly for Hue, since CirclePosition is 0.0-1.0
    double Hue = CirclePosition[i]; 
    // Make sure we don't get an overflow issue
    Hue = constrain(Hue, 0.0, 1.0);
    // Convert the CirclePosition-Hue to RDRB for the dmx
    HSL2RGB(colors, Hue, 1.0, 0.6);    
    // Send the colors to the fixture
    setDmxValues(colors, channel);    
    // Move to next fixture
    channel += 4;
  }
}


// Given H,S,L in range of 0-1
// Returns a Color (RGB struct) in range of 0-255
void HSL2RGB(byte rgb[], double h, double sl, double l)
{
  double v;
  double r,g,b;

  r = l;   // default to gray
  g = l;
  b = l;
  v = (l <= 0.5) ? (l * (1.0 + sl)) : (l + sl - l * sl);
  if (v > 0)
  {
    double m;
    double sv;
    int sextant;
    double fract, vsf, mid1, mid2;

    m = l + l - v;
    sv = (v - m ) / v;
    h *= 6.0;
    sextant = (int)h;
    fract = h - sextant;
    vsf = v * sv * fract;
    mid1 = m + vsf;
    mid2 = v - vsf;
    switch (sextant)
    {
    case 0:
      r = v;
      g = mid1;
      b = m;
      break;
    case 1:
      r = mid2;
      g = v;
      b = m;
      break;
    case 2:
      r = m;
      g = v;
      b = mid1;
      break;
    case 3:
      r = m;
      g = mid2;
      b = v;
      break;
    case 4:
      r = mid1;
      g = m;
      b = v;
      break;
    case 5:
      r = v;
      g = m;
      b = mid2;
      break;
    }
  }
  //ColorRGB rgb;

  rgb[0] = byte(r * 255.0f);
  rgb[1] = byte(g * 255.0f);
  rgb[2] = byte(b * 255.0f);

}


void dumpAnalogReadings(){
  for(int i=0;i<6;i++){
    Serial.println(AnalogInReading[i]);
  }
  Serial.println("---");
}

void FictionalRotationValues(){
  RotationSpeed[0] = 0;
  RotationSpeed[1] = 100;
  RotationSpeed[2] = 200;
  RotationSpeed[3] = 300;
  RotationSpeed[4] = 400;
  RotationSpeed[5] = 500;  
}