SHARE
TWEET

Klipsch ProMedia 5.1 Control Pod Replacement code

Zurcher Jun 5th, 2012 555 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1.  /*
  2.   *  Arduino as a replacement for the Klipsch Promedia (Ultra) 5.1 Control Pod
  3.   *
  4.   *  I HAVE ONLY TESTED THIS ON THE ULTRA, BUT IT SHOULD BE COMPATIBLE
  5.   *  WITH THE STANDARD SYSTEM TOO, ACCORDING TO THIS:
  6.   *  http://www.thompdale.com/bash_amplifier/5-1/Control_pods/5-1_control_pod.htm
  7.   *
  8.   *  Sketch: KlipschPM51UltraControlPod.ino
  9.   *  Date: 2012-06-05
  10.   *
  11.   *  Author: Christopher Zurcher
  12.   *  Email: zurcher+klipschpod@gmail.com
  13.   *
  14.   *  Credits:
  15.   *  Dale Thompson and Evan Shultz for the schematics:
  16.   *  http://www.thompdale.com/bash_amplifier/5-1/5-1_bash_amp.htm
  17.   *
  18.   *  Ken Shirriff for the IR Receiver library:
  19.   *  http://www.arcfn.com/2009/08/multi-protocol-infrared-remote-library.html
  20.   *
  21.   *  Arduino Playground for the Timer1 Library
  22.   *  http://arduino.cc/playground/Code/Timer1
  23.   *
  24.   */
  25.  
  26.   #include <Wire.h> //I2C library
  27.   #include <TimerOne.h>
  28.   #include <IRremote.h> //IRremote uses Timer2
  29.  
  30.  
  31.   /////////////
  32.  //CONSTANTS//
  33. /////////////
  34.   #define ATT_MAX VOL_MAX-VOL_MIN //Attenuation
  35.   #define ATT_MIN 0
  36.   #define VOL_MAX 80          //Goes to 80, as the factory box does
  37.   #define VOL_MIN 1           //PT2258 attenuates down to -79db
  38.   #define OFFSET_MAX 15       //As per factory box
  39.   #define OFFSET_MIN -15      //Factory box was limited to -9 because only 2 characters
  40.  
  41.   #define OUT_PWR A0          //Naming pins
  42.   #define OUT_HP A1
  43.   #define SWITCH_0 A2
  44.   #define SWITCH_1 A3
  45.  
  46.   #define STATE_OFF 0         //Power States
  47.   #define STATE_PWR 1
  48.   #define STATE_HP 2
  49.  
  50.   #define MODE_OFF 0          //Modes for if we are changing any of the offsets
  51.   #define MODE_REAR 13
  52.   #define MODE_CENTER 14
  53.   #define MODE_SUB 15
  54.  
  55.   #define SEVEN_SEG_FIRST_PIN 2 //First pin of 7-seg pins (left pins 0 and 1 open for Serial)
  56.   #define SEVEN_SEG_STROBE_OFFSET 9 //Beginning of character select pins
  57.  
  58.   ///////////
  59.  //GLOBALS//
  60. ///////////
  61.   int deviceAddress = (0x88 >> 1); //I2C address of the volume control chip (PT2258)
  62.                              //Shifted right because the Wire library wants a 7-bit address
  63.   unsigned long offsetTime = 0; //Values used to time-out the display of offset volumes
  64.   unsigned long offsetDelay = 3500; //In milliseconds
  65.   //Initialize at minimum volume; never liked the factory setting of 40
  66.   int volume = VOL_MIN; //"Current" volume
  67.   int volPWR = VOL_MIN; //Stores PWR state volume when in HP mode
  68.   int volHP = VOL_MIN; //Stores HP state volume when in PWR mode
  69.   int offsetRear = 0;
  70.   int offsetCenter = 0;
  71.   int offsetSub = 0;
  72.   int offsetMode = MODE_OFF;
  73.   int state = STATE_OFF; //Initialize system as OFF
  74.   boolean switch0 = false;
  75.   boolean switch1 = false;
  76.   boolean sevenSegOn = true;
  77.  
  78.   int RECV_PIN = 13; //Input pin from IR receiver
  79.   IRrecv irrecv(RECV_PIN);
  80.   decode_results results;
  81.  
  82.   byte sevenSegPosition = 0;
  83.   byte sevenSegChars[4] = {10,10,10,10}; //Initialize as blank
  84.   byte sevenSegDigits[17][7] = {{ 1,1,1,1,1,1,0 },  // = 0
  85.                                 { 0,1,1,0,0,0,0 },  // = 1
  86.                                 { 1,1,0,1,1,0,1 },  // = 2
  87.                                 { 1,1,1,1,0,0,1 },  // = 3
  88.                                 { 0,1,1,0,0,1,1 },  // = 4
  89.                                 { 1,0,1,1,0,1,1 },  // = 5
  90.                                 { 1,0,1,1,1,1,1 },  // = 6
  91.                                 { 1,1,1,0,0,0,0 },  // = 7
  92.                                 { 1,1,1,1,1,1,1 },  // = 8
  93.                                 { 1,1,1,0,0,1,1 },  // = 9
  94.                                 { 0,0,0,0,0,0,0 },  // = 10 Blank
  95.                                 { 0,1,1,0,1,1,1 },  // = 11 H (for headphones)
  96.                                 { 1,1,0,0,1,1,1 },  // = 12 P
  97.                                 { 0,0,0,0,1,0,1 },  // = 13 r //Rear
  98.                                 { 1,0,0,1,1,1,0 },  // = 14 C //Center
  99.                                 { 1,0,1,1,0,1,1 },  // = 15 S //Sub
  100.                                 { 0,0,0,0,0,0,1 }}; // = 16 - //Negative
  101.  
  102.   //////////////
  103.  //INTERRUPTS//
  104. //////////////
  105.   //Timer1 ISR
  106.   void updateSevenSeg()
  107.   {//Serial.println("updateSevenSeg()");
  108.     digitalWrite(SEVEN_SEG_STROBE_OFFSET+(sevenSegPosition++),HIGH); //Turn off previous char
  109.     sevenSegPosition &= 0x03; //Limit character select to 0-3
  110.     sevenSegWrite(sevenSegOn?sevenSegChars[sevenSegPosition]:10); //Load the next char
  111.     digitalWrite(SEVEN_SEG_STROBE_OFFSET+(sevenSegPosition),LOW); //Turn on next char
  112.   }
  113.  
  114.   /////////
  115.  //SETUP//
  116. /////////
  117.   //This runs when you first plug in the system to power, not every time you "power-on"
  118.   // because the Control pod is run on a constant 5V standby power
  119.   void setup()
  120.   {
  121.     //Serial.begin(9600); //For debugging
  122.  
  123.     irrecv.enableIRIn(); // Start the IR Remote receiver
  124.    
  125.     //Setup pins for PWR_MODE and HP_MODE control
  126.     pinMode(OUT_PWR, OUTPUT);
  127.     pinMode(OUT_HP, OUTPUT);
  128.     digitalWrite(OUT_PWR, HIGH); //Active Low
  129.     digitalWrite(OUT_HP, HIGH); //Active Low
  130.    
  131.     //Setup pins for the unused switches
  132.     pinMode(SWITCH_0, INPUT);
  133.     pinMode(SWITCH_1, INPUT);
  134.     digitalWrite(SWITCH_0, HIGH); //Internal Pull-Up
  135.     digitalWrite(SWITCH_1, HIGH); //Internal Pull-Up
  136.    
  137.     //Setup pins for SevenSeg display
  138.     for(int i = SEVEN_SEG_FIRST_PIN; i < SEVEN_SEG_FIRST_PIN+11 ; i++)
  139.     {
  140.       pinMode(i, OUTPUT);
  141.       digitalWrite(i, HIGH); //Off, for now
  142.     }
  143.  
  144.     //Digital Pin RECV_PIN is the input coming from the IR receiver module
  145.     pinMode(RECV_PIN, INPUT);
  146.     digitalWrite(RECV_PIN, LOW); //No Internal Pull-Up
  147.  
  148.     delay(200); //Allow power-up of PT2258
  149.     Wire.begin(); //Initialise the connection
  150.     mute();
  151.     setVolumeHP(); //Set volumes to 0
  152.  
  153.     Timer1.initialize(2500); //Set a timer length of 2500 microseconds
  154.                              // (2.5 milliseconds) for sevenSeg character strobe
  155.     Timer1.attachInterrupt(updateSevenSeg); //Attach sevenSeg update as Timer1 ISR
  156.   }
  157.  
  158.   ////////////////////
  159.  //LOOP a.k.a. MAIN//
  160. ////////////////////
  161.   void loop()
  162.   {//Serial.println("loop()");
  163.     //-----------IR REMOTE CODES----------------------------------
  164.     if ((state == STATE_OFF) && irrecv.decode(&results))
  165.     {
  166.       sevenSegOn = true; //Re-enable the display, in case it was left off at power-down
  167.       switch(results.value) //Switch on received remote code
  168.       {
  169.         case 0xA90: //Power
  170.           statePWR();
  171.           break;
  172.  
  173.         case 0xDD0: //Recall
  174.           stateHP();
  175.           break;
  176.  
  177.         default:
  178.           break;
  179.       }
  180.       delay(250);
  181.       irrecv.resume(); // Reset to receive the next value
  182.     }
  183.     else if ((state != STATE_OFF) && irrecv.decode(&results)) //Check if new remote code
  184.     {
  185.       switch(results.value) //Do stuff based on what button was pushed
  186.       {
  187.         case 0xA90: //Power
  188.           stateOFF();
  189.           break;
  190.  
  191.         case 0xDD0: //Recall
  192.           if(state == STATE_PWR)
  193.           {
  194.             stateHP();
  195.           }
  196.           else if(state == STATE_HP)
  197.           {
  198.             statePWR();
  199.           }
  200.           break;
  201.  
  202.         case 0x290: //Mute
  203.           volume = VOL_MIN;
  204.           setVolume();
  205.           break;
  206.  
  207.         case 0x5D0: //Display
  208.           sevenSegOn = !sevenSegOn; //Hide/show display
  209.           break;
  210.  
  211.         case 0x490: //Volume +
  212.           volume++;
  213.           setVolume();
  214.           break;
  215.  
  216.         case 0xC90: //Volume -
  217.           volume--;
  218.           setVolume();
  219.           break;
  220.  
  221.         case 0x090: //Channel +
  222.           volume = ((volume+5)/5)*5; //Up to nearest 5
  223.           setVolume();
  224.           break;
  225.  
  226.         case 0x890: //Channel -
  227.           volume = (volume%5)?(volume-(volume%5)):(volume-5); //Down to nearest 5
  228.           setVolume();
  229.           break;
  230.  
  231.         case 0x010: //NumPad 1
  232.           offsetModeOn(MODE_REAR, offsetRear+1); //Increase offset by 1
  233.           break;
  234.  
  235.         case 0xC10: //NumPad 4
  236.           if (offsetMode == MODE_REAR) //If already in this offset mode
  237.             offsetModeOff(); //Return to regular volume display
  238.           else
  239.             offsetModeOn(MODE_REAR, offsetRear); //Show current offset
  240.           break;
  241.  
  242.         case 0x610: //NumPad 7
  243.           offsetModeOn(MODE_REAR, offsetRear-1); //Decrease offset by 1
  244.           break;
  245.  
  246.         case 0x810: //NumPad 2
  247.           offsetModeOn(MODE_CENTER, offsetCenter+1); //Increase offset by 1
  248.           break;
  249.  
  250.         case 0x210: //NumPad 5
  251.           if (offsetMode == MODE_CENTER) //If already in this offset mode
  252.             offsetModeOff(); //Return to regular volume display
  253.           else
  254.             offsetModeOn(MODE_CENTER, offsetCenter); //Show current offset
  255.           break;
  256.  
  257.         case 0xE10: //NumPad 8
  258.           offsetModeOn(MODE_CENTER, offsetCenter-1); //Decrease offset by 1
  259.           break;
  260.  
  261.         case 0x410: //NumPad 3
  262.           offsetModeOn(MODE_SUB, offsetSub+1); //Increase offset by 1
  263.           break;
  264.  
  265.         case 0xA10: //NumPad 6
  266.           if (offsetMode == MODE_SUB) //If already in this offset mode
  267.             offsetModeOff(); //Return to regular volume display
  268.           else
  269.             offsetModeOn(MODE_SUB, offsetSub); //Show current offset
  270.           break;
  271.  
  272.         case 0x110: //NumPad 9
  273.           offsetModeOn(MODE_SUB, offsetSub-1); //Decrease offset by 1
  274.           break;
  275.  
  276.         default:
  277.           break;
  278.       }
  279.       delay(250); //So you don't accidentally turn it up instantly to 80
  280.       irrecv.resume(); // Reset to receive the next value
  281.     }
  282.  
  283.     //This switches the display back to main volume after offsetDelay time has passed
  284.     if(offsetMode)
  285.     {
  286.       if((millis() - offsetTime) > offsetDelay)
  287.       {
  288.         sevenSegChars[0] = 10;
  289.         sevenSegChars[1] = 10;
  290.         sevenSegChars[2] = volume/10;
  291.         sevenSegChars[3] = volume%10;
  292.         offsetMode = MODE_OFF;
  293.       }
  294.     }
  295.   }//END LOOP
  296.  
  297.  
  298.   //Helps with serial output: allows for leading zeroes
  299.   //Only used for debugging
  300.   //Code from here: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1279500031
  301.   // (Reply #1, by "bootsector")
  302.   void printHex(int num)
  303.   {
  304.     char tmp[16];
  305.     char format[128];
  306.     sprintf(format, "0x%%.%dX", 2);
  307.  
  308.     sprintf(tmp, format, num);
  309.     Serial.println(tmp);
  310.   }
  311.  
  312.  
  313.   /////////////
  314.  //Functions//
  315. /////////////
  316.   void sevenSegWrite(byte digit)
  317.   {//Serial.print("sevenSegWrite(byte digit)");
  318.     for (byte segCount = 0; segCount < 7; segCount++)
  319.     {
  320.       digitalWrite(segCount+SEVEN_SEG_FIRST_PIN, !sevenSegDigits[digit][segCount]);
  321.       //The "!" is because the segments are active low, but they are stored here in the code
  322.       //in the array sevenSegDigits[][] the other way around for easier readability
  323.     }
  324.     return;
  325.   }
  326.  
  327.   void mute()
  328.   {//Serial.println("mute()");
  329.     Wire.beginTransmission(deviceAddress);
  330.     Wire.write(0xF9); //MUTE ON
  331.     Wire.endTransmission();
  332.     return;
  333.   }
  334.  
  335.   void unmute()
  336.   {//Serial.println("unmute()");
  337.     Wire.beginTransmission(deviceAddress);
  338.     Wire.write(0xF8); //MUTE OFF
  339.     Wire.endTransmission();
  340.     return;
  341.   }
  342.  
  343.   void stateHP()
  344.   {//Serial.println("stateHP()");
  345.     state = STATE_HP;
  346.     mute();
  347.     offsetModeOff(); //In case user switches to HP before offsetMode times out
  348.     digitalWrite(OUT_PWR, HIGH); //Disable speaker amps
  349.     delayMicroseconds(15);  //Empirical
  350.     digitalWrite(OUT_HP, LOW); //Enable headphone amp
  351.     for(int i=0; i < 8; i++) //Blink the "HP" label while we wait for the power-amp to drain
  352.     {
  353.       sevenSegChars[2] = (i%2)?11:10;
  354.       sevenSegChars[3] = (i%2)?12:10;
  355.       delay(400); //400*8 for a total of 3.2 seconds, same delay as original
  356.     }
  357.     sevenSegChars[2] = volume/10;
  358.     sevenSegChars[3] = volume%10;
  359.     volume = volHP; //Restore headphone state volume
  360.     setVolume(); //set volume to stored HP volume level
  361.     unmute();
  362.     return;
  363.   }
  364.  
  365.   void statePWR()
  366.   {//Serial.println("statePWR()");
  367.     state = STATE_PWR;
  368.     mute();
  369.     sevenSegChars[2] = volume/10;
  370.     sevenSegChars[3] = volume%10;
  371.     digitalWrite(OUT_HP, HIGH); //Disable headphone amp
  372.     volume = volPWR; //Restore power state volume
  373.     setVolume(); //set volume to stored PWR volume level
  374.     unmute();
  375.     delay(300); //wait for things to settle
  376.     digitalWrite(OUT_PWR, LOW); //Enable speaker amps
  377.     return;
  378.   }
  379.  
  380.   void stateOFF()
  381.   {//Serial.println("stateOFF()");
  382.     state = STATE_OFF;
  383.     mute();
  384.     offsetMode = MODE_OFF; //In case user switches off before offsetMode times out
  385.     for(int i=0; i<=3; i++)
  386.     { //Blank out Display
  387.       sevenSegChars[i] = 10;
  388.     }
  389.     digitalWrite(OUT_HP, HIGH); //Disable headphone amp
  390.     digitalWrite(OUT_PWR, HIGH); //Disable speaker amps
  391.  
  392.  
  393.     volPWR = VOL_MIN; //These 2 lines set your volumes back to minimum on power-down.
  394.     volHP = VOL_MIN;  //I like this because you don't power-on only to accidentally go deaf
  395.                       // if your roommates left it set super loud when they turned it off.
  396.     //If you like your volume to remember where you left it, comments these out.
  397.  
  398.     delay(5000); //Don't let system turn back on for 5 seconds
  399.     return;
  400.   }
  401.  
  402.   void offsetModeOff()
  403.   {//Serial.println("offsetModeOff()");
  404.     offsetTime -= offsetDelay;
  405.     sevenSegChars[0] = 10;
  406.     sevenSegChars[1] = 10;
  407.     sevenSegChars[2] = volume/10;
  408.     sevenSegChars[3] = volume%10;
  409.     return;
  410.   }
  411.  
  412.   void offsetModeOn(int mode, int offset)
  413.   {//Serial.println("offsetModeOn()");
  414.     if(state != STATE_PWR)
  415.     { //Don't do anything if not in powered-speaker mode
  416.       return;
  417.     }
  418.  
  419.     offsetMode = mode; //Set global mode
  420.     offset = constrain(offset, OFFSET_MIN, OFFSET_MAX);
  421.  
  422.     switch(mode)
  423.     {
  424.       case MODE_REAR:
  425.         offsetRear = offset;
  426.         setVolume(); //Apply whatever offset change has occurred
  427.         sevenSegChars[0] = MODE_REAR; //r
  428.         break;
  429.  
  430.       case MODE_CENTER:
  431.         offsetCenter = offset;
  432.         setVolume();
  433.         sevenSegChars[0] = MODE_CENTER; //C
  434.         break;
  435.  
  436.       case MODE_SUB:
  437.         offsetSub = offset;
  438.         setVolume();
  439.         sevenSegChars[0] = MODE_SUB; //S
  440.         break;
  441.  
  442.       default:
  443.         break;
  444.     }
  445.  
  446.     sevenSegChars[1] = (offset < 0)?16:10; //- For negative numbers
  447.     sevenSegChars[2] = abs(offset)/10;
  448.     sevenSegChars[3] = abs(offset)%10;
  449.  
  450.     offsetTime = millis(); //Mark when offsetMode was most recently triggered
  451.     return;
  452.   }
  453.  
  454.   void setVolume()
  455.   {//Serial.println("setVolume()");
  456.     volume = constrain(volume, VOL_MIN, VOL_MAX); //Make sure volume is within limits
  457.     if(state == STATE_PWR)
  458.     {
  459.       if(offsetMode) //Clear out the offset labeling
  460.       {
  461.         offsetModeOff();
  462.       }
  463.       volPWR = volume; //Stored so it can be retrieved after returning from HP mode
  464.       setVolumePWR();
  465.     }
  466.     else
  467.     {
  468.       volHP = volume;
  469.       setVolumeHP();
  470.     }
  471.     //Update Display
  472.     sevenSegChars[2] = volume/10;
  473.     sevenSegChars[3] = volume%10;
  474.     return;
  475.   }
  476.  
  477.   void setVolumePWR()
  478.   {//Serial.println("setVolumePWR()");
  479.     int att = VOL_MAX - volume; //Need to send attenuation value, not volume, to PT2258
  480.     int tempAtt;
  481.     int frontTens;
  482.     int frontOnes;
  483.     int rearTens;
  484.     int rearOnes;
  485.  
  486.     Wire.beginTransmission(deviceAddress);
  487.  
  488.     frontTens = att/10;
  489.     frontOnes = att%10;
  490.     Wire.write( 0x80 | frontTens ); //FR -10db
  491.     Wire.write( 0x90 | frontOnes ); //    -1db
  492.    
  493.     Wire.write( 0x40 | frontTens ); //FL
  494.     Wire.write( 0x50 | frontOnes );
  495.  
  496.     tempAtt = constrain(att-offsetRear, ATT_MIN, ATT_MAX);
  497.     rearTens = tempAtt/10;
  498.     rearOnes = tempAtt%10;
  499.     Wire.write( 0x00 | rearTens ); //RR
  500.     Wire.write( 0x10 | rearOnes );
  501.    
  502.     Wire.write( 0x20 | rearTens ); //RL
  503.     Wire.write( 0x30 | rearOnes );
  504.    
  505.     tempAtt = constrain(att-offsetCenter, ATT_MIN, ATT_MAX);
  506.     Wire.write( 0x60 | (tempAtt/10) ); //FC
  507.     Wire.write( 0x70 | (tempAtt%10) );
  508.    
  509.     tempAtt = constrain(att-offsetSub, ATT_MIN, ATT_MAX);
  510.     Wire.write( 0xA0 | (tempAtt/10) ); //LFE
  511.     Wire.write( 0xB0 | (tempAtt%10) );
  512.    
  513.     Wire.endTransmission();
  514.     return;
  515.   }
  516.  
  517.   void setVolumeHP()
  518.   {//Serial.println("setVolumeHP()");
  519.     int att = VOL_MAX - volume;
  520.     int tens = att/10;
  521.     int ones = att%10;
  522.  
  523.     Wire.beginTransmission(deviceAddress);
  524.  
  525.     Wire.write( 0x80 | tens ); //FR -10db
  526.     Wire.write( 0x90 | ones ); //    -1db
  527.    
  528.     Wire.write( 0x40 | tens ); //FL
  529.     Wire.write( 0x50 | ones );
  530.  
  531.     Wire.write( 0x00 | tens ); //RR
  532.     Wire.write( 0x10 | ones );
  533.    
  534.     Wire.write( 0x20 | tens ); //RL
  535.     Wire.write( 0x30 | ones );
  536.    
  537.     Wire.write( 0x60 | tens ); //FC
  538.     Wire.write( 0x70 | ones );
  539.    
  540.     Wire.write( 0xA7 ); //LFE set to max attenuation
  541.     Wire.write( 0xB9 ); //...as done in original controller
  542.    
  543.     Wire.endTransmission();
  544.     return;
  545.   }
RAW Paste Data
Top