Don's Pages and my Music

Wednesday, August 10, 2011

Marklar Hacked a stereo to use face plate to Control LED lighting with an old stereo receiver

marklar Hacked a stereo to use face plate to Control LED lighting with an old stereo receiver






Here is a video of how I then used this stereo panel to control my lighting system.




Video Links...
http://youtu.be/PoX4be7YvoY

http://www.youtube.com/watch?v=PoX4be7YvoY&feature=player_embedded




This post first describes the stereo I hacked and provides some classes written to help use the hacked hardware.  The followup post is the complete code and some related notes.  Feedback welcome.

Overview
I needed an IR Receiver and Radio Shack was fresh out and I did not want to wait for delivery.  I had an old stereo that I was about to junk, so I pulled the IR Receiver out and it worked great. 

Wow, look at all the stuff...
While in there, I noticed the volume knob (really nice rotary encoder) was on a break out board with some buttons. 

I had not used a rotary encoder before so now was my chance.  Using this link (http://www.arduino.cc/playground/Main/RotaryEncoders) I had the rotary encoder working in no time.  With some forum help I was able to get the three buttons working as well.  These are buttons connected by different resistor levels so a single analog pin can read the "bank" of buttons - a very common approach for button reading. 

Next I noticed the huge button panel for the front face and another cool rotary encoder were setup as pretty much the same animal.  After following the super highway around the board I was able to determine which buttons where controlled by which output and playing told me which wires controlled the second rotary encoder.



So with control of all the buttons, both rotary encoders and even the illuminated power button led it was time to get the code working.

I am an application architect but C++ is my weakest language, so there is my disclaimer. smiley

First I had to create some classes to get the resistor button arrays and rotary encoder processing black boxed.

Analog Button Controller
The concept here is that multiple sets of buttons can be read by a separate analog inputs and can work either in tandem or stand alone.  I do not want to use the memory to store values in an array, so I want to use code to determine the button value instead of a commonly used array look up table.  For this reason I use a callback function to return the button based on the analog value.  A callback is also used when a valid button is pressed.  This allows the code to put into a library and only the base mechanism used.

Code:
class AnalogButtonController { private:   byte btnPressed;   byte lastButtonPressed;   byte btnPressedCache;  public:   byte btnPin;   void (*cmdButtonPressed)(int button);   int (*cmdGetButtonValue)(int virtualValue);    AnalogButtonController(void (*buttonPressedCallback)(int button), int (*getButtonValue)(int virtualValue), byte thePin) {     this->cmdButtonPressed = buttonPressedCallback;     this->cmdGetButtonValue = getButtonValue;     btnPressed = 0;     btnPressedCache = 0;     lastButtonPressed = 0;     btnPin = thePin;   };    ~AnalogButtonController() {   };    void checkButton(){     int tmpVal = cmdGetButtonValue(analogRead(btnPin));     //--- if an invalid range is returned then ignore it to not cause button to multi-click     if( tmpVal < 0 ) return;            if (tmpVal == btnPressedCache){        btnPressed = tmpVal;     } else {        btnPressedCache = tmpVal;     }    }    void runButtonPress(){     if( lastButtonPressed == btnPressed) return;     lastButtonPressed = btnPressed;     if( btnPressed == 0) return;     cmdButtonPressed(btnPressed);   }  }; 

Discovery Phase
The values from the analog inputs jump around some when pressed.  So the first thing I needed to know what what range to look for to determine what button is pressed.  I created this simple program which allowed me to hold down a button for a bit, then let up.  It would tell me the high and low .. the buttons voltage range.  Using the results allows me to code the callback routines.

Discovery Code:

Code:
/* Get Analog Value Range */  void setup() {   Serial.begin(9600); }  boolean inDown = false; int minVal = 20000; int maxVal = 0;  void loop() {   int tmpVal = analogRead(5);   if (tmpVal < 1000){     if( tmpVal < minVal)       minVal = tmpVal;     if( tmpVal > maxVal)       maxVal = tmpVal;     inDown = true;   } else {     if (inDown){       Serial.println(minVal, DEC);       Serial.println(maxVal, DEC);       Serial.println("===");       minVal = 20000;       maxVal = 0;     }     inDown = false;   }   delay(50);  //maybe remove this 


Rotary Encoder Controller
The concept here is a Rotary Encoder can be set up with a range and optionally a tick amount and direction orientation.  Then when the dial changes, resulting an actual change due to not being stopped at max or min, a callback is called.


Code:
class RotaryEncoderRangeController { private:   boolean encoderForward;   int encoderPos;   int encoderPinALast;   int encoderMin;   int encoderMax;   byte encoderPinA;   byte encoderPinB;  public:   byte encoderIncr;      void (*cmdValueChanged)(int newValue);    RotaryEncoderRangeController(void (*valueChangedCallback)(int newValue), byte thePinA, byte thePinB) {     this->cmdValueChanged = valueChangedCallback;     encoderPinA = thePinA;     encoderPinB = thePinB;   };    ~RotaryEncoderRangeController() {   };    void begin(){     encoderForward = true;     encoderIncr = 1;     encoderPos = 0;     encoderPinALast = LOW;     pinMode (encoderPinA,INPUT);     pinMode (encoderPinB,INPUT);     encoderMin = -30000;     encoderMax = 30000;   }    void begin(int theMinValue, int theMaxValue, int theCurrentVal, byte theIncrement){      begin();      setRange(theMinValue,theMaxValue,theCurrentVal,theIncrement);   }   void begin(int theMinValue, int theMaxValue, int theCurrentVal, byte theIncrement, boolean theIsForward){      begin();      setRange(theMinValue,theMaxValue,theCurrentVal,theIncrement,theIsForward);   }      void setRange(int theMinValue, int theMaxValue, int theCurrentVal, byte theIncrement){     setRange(theMinValue,theMaxValue,theCurrentVal);     setIncrement(theIncrement);   }   void setRange(int theMinValue, int theMaxValue, int theCurrentVal, byte theIncrement, boolean theIsForward){     setRange(theMinValue,theMaxValue,theCurrentVal, theIncrement);     encoderForward = theIsForward;   }    void setRange(int theMinValue, int theMaxValue, int theCurrentVal){     encoderMin = theMinValue;     encoderMax = theMaxValue;     setPos(theCurrentVal);   }    void setRange(int theMinValue, int theMaxValue){     encoderMin = theMinValue;     encoderMax = theMaxValue;     //--- to make sure in range     encoderPos = setPos(encoderPos);   }    int setIncrement(byte theIncrement){    encoderIncr = theIncrement;   }      int setPos(int theNewPos){     int tmpVal = theNewPos;     if (tmpVal < encoderMin)       tmpVal = encoderMin;     if (tmpVal > encoderMax)       tmpVal = encoderMax;                 if (tmpVal != encoderPos){       encoderPos = tmpVal;       cmdValueChanged(encoderPos);     }       return encoderPos;   }    int getPos(){     int tmpVal = encoderPos;     if (tmpVal < encoderMin)       tmpVal = encoderMin;     if (tmpVal > encoderMax)       tmpVal = encoderMax;     return tmpVal;   }    void check(){     int tmpV = digitalRead(encoderPinA);     int tmpNew = tmpV;     int tmpCurr = encoderPos;     if ((encoderPinALast == LOW) && (tmpV == HIGH)) {              if (digitalRead(encoderPinB) == LOW) {         if( encoderForward ){           tmpCurr += encoderIncr;         } else {           tmpCurr -= encoderIncr;         }       }        else {         if( encoderForward ){           tmpCurr -= encoderIncr;         } else {           tmpCurr += encoderIncr;         }       }       tmpNew = setPos(tmpCurr);     }      encoderPinALast = tmpNew;   }  }; 






Full Demo
This code reads all 24 buttons on the main panel (1-24), the power and mute buttons (35,34), the three buttons around the volume knob (31,32,33) and the button in the center of the rotary encoder labeled set (30).  Also the small rotary encoder is re1 and the larger volume knob is re2.  This demo simply displays the button pressed or new value of a knob turn via the serial port.

This uses timer2 to allow for background reading of the values to not effect the loop.  You can move this to any timer / fast read iteration.


Code:
*** Include - code from class AnalogButtonController here *** Include - code from class RotaryEncoderRangeController here  //====================================  #include <MsTimer2.h>  //---------------------------- // Rotary Encoder Setup //----------------------------  //--- create callback functions for Rotary encoder void valueChangedForRE1(int theNewValue){   Serial.print("RE1 ");   Serial.println(theNewValue, DEC); } void valueChangedForRE2(int theNewValue){   Serial.print("RE2 ");   Serial.println(theNewValue, DEC); }  //--- create a new Rotary encoder controller for RE1 using digital pins RotaryEncoderRangeController re1 = RotaryEncoderRangeController(&valueChangedForRE2, 8, 9); RotaryEncoderRangeController re2 = RotaryEncoderRangeController(&valueChangedForRE1, 6, 7);  //---------------------------- // Button Set Setup //---------------------------- //--- create callback functions for button group boolean tmpStatus = true; void buttonPressed(int theButton){   Serial.print("Button ");   Serial.println(theButton, DEC); }  int getButtonFromValueSet1(int theVal){ if( theVal > 1010 ) return 0;  if( theVal <= 930 && theVal >= 920 ) return 5; if( theVal <= 950 && theVal >= 942 ) return 6; if( theVal <= 970 && theVal >= 964 ) return 7; if( theVal <= 976 && theVal >= 972 ) return 8; if( theVal <= 200 && theVal >= 2 ) return 13; if( theVal <= 877 && theVal >= 870 ) return 14; if( theVal <= 965 && theVal >= 953 ) return 15; if( theVal <= 1000 && theVal >= 992 ) return 31; if( theVal <= 992 && theVal >= 984 ) return 32; if( theVal <= 982 && theVal >= 978 ) return 33;    return -1; }  int getButtonFromValueSet2(int theVal){ if( theVal > 1010 ) return 0;   if( theVal <= 980 && theVal >= 974 ) return 1; if( theVal <= 972 && theVal >= 969 ) return 2; if( theVal <= 967 && theVal >= 962 ) return 3; if( theVal <= 960 && theVal >= 951 ) return 4; if( theVal <= 105 && theVal >= 0 ) return 9; if( theVal <= 873 && theVal >= 868 ) return 10; if( theVal <= 926 && theVal >= 919 ) return 11; if( theVal <= 947 && theVal >= 939 ) return 12; if( theVal <= 988 && theVal >= 982 ) return 34; if( theVal <= 996 && theVal >= 990 ) return 35;    return -1; }  int getButtonFromValueSet3(int theVal){ if( theVal > 1010 ) return 0;   if( theVal <= 880 && theVal >= 870 ) return 17; if( theVal <= 928 && theVal >= 920 ) return 18; if( theVal <= 964 && theVal >= 960 ) return 19; if( theVal <= 974 && theVal >= 968 ) return 20; if( theVal <= 200 && theVal >= 0 ) return 21; if( theVal <= 949 && theVal >= 941 ) return 22; if( theVal <= 961 && theVal >= 954 ) return 23; if( theVal <= 982 && theVal >= 977 ) return 24; if( theVal <= 997 && theVal >= 989 ) return 16; if( theVal <= 991 && theVal >= 984 ) return 30;    return -1; }   //--- Create three sets of button controlles that all read different values but call the same callback function //-- Example: The first one .. //      calls buttonPressed when button pressed,  //      gets button from analog value from getButtonFromValueSet1 function  //      reads analog pin 3  AnalogButtonController set1 = AnalogButtonController(&buttonPressed, &getButtonFromValueSet1, 3); AnalogButtonController set2 = AnalogButtonController(&buttonPressed, &getButtonFromValueSet2, 4); AnalogButtonController set3 = AnalogButtonController(&buttonPressed, &getButtonFromValueSet3, 5);  //----------------------------   void processTimer2(){   re1.check();    re2.check();    set1.checkButton();    set2.checkButton();    set3.checkButton();  }  void setup(){   Serial.begin(19200);    //--- Range from 1 to 256 with tick of 1 and step of 5   re1.begin(1,256,1,5);   //--- Same as above but goes backwards   re2.begin(1,256,1,5,false);   //Note: or setup range on the fly (for different usages of same encoder)   //Example:  re1.setRange(10,10000,currentValueVar,50);    MsTimer2::set(5, processTimer2);   MsTimer2::start(); }   void loop(){  //--- take action in loop to not blow out interrupt time  set1.runButtonPress();  set2.runButtonPress();  set3.runButtonPress();  delay(100); }    
Notes about the code:

I wanted the code to be "load and go" for someone making use of it.  Also this is Alpha version one, not quite ready to move into library form.  For these reasons the classes are just placed in line at the top of the code.

The buttons on the stereo are not connected to outputs in the same logical orientation I want to use them in.  For example, the top eight buttons on the stereo go to 2 different outputs.  I opted to create a logical breakdown of button numbers from left to right.  So the top 8 are 1-8 the next row is 9-16 and so on.  That is why the return numbers in the callbacks that map analog readings jump around.  Your usage of this library may be simply numbered in order.

The action resulting from the callback should not happen in a timer or your code will lock up trying to do too much in the cycle.  For this reason the runButtonPress routine is called inside the standard loop so the callback will run there as well.


Code:
// this is in loop   set1.runButtonPress();  set2.runButtonPress();  set3.runButtonPress(); 
 







Read More and get the Code...
http://arduino.cc/forum/index.php/topic,67382.0.html

I really like this one. I have a bunch of old broken electronics devices in my Garage and have been wanting to learn how to Re-purpose them for a long time...

Thanks!:)

Don


Control LED lighting with an old stereo receiver
Control LED lighting with an old stereo receiver - Hack a Day
Hacked a stereo to use face plate
Hacked a Stereo Panel - Now Lighting Control Panel - YouTube
Arduino playground - RotaryEncoders

No comments:

Post a Comment