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.
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.
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:
/* 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.
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; } };
No comments:
Post a Comment