Skip to content

August 9, 2011

Turning it up to 12

I bought a nice Samsung flat screen from Best Buy during a Black Friday sale and have been mostly happy with it. The built-in speakers are terrible though. Don’t get me wrong, I’m no audiophile. I went on a bus tour of Toledo two years ago and still use the free headphones they gave us. The sound on my TV was worse though.
DSC_0004
I lived with it for a while, but eventually decided that I needed to give myself an auditory experience that matched the visual experience of watching my new TV. I had heard good things about the Bose Companion 3 Series II multimedia speaker system, so I grabbed it on sale from Target.

I knew the speaker system was meant for a computer, but didn’t think it made any difference. My TV has an audio-out jack, the speakers have an audio-in jack, what could go wrong? I got the speakers home and connected them to the TV. The sound was amazing. My ears ached retroactively from the abuse they had been subjected to from the built-in speakers. Plugging in the new speakers turned Snooki into a virtuoso, Walter White into Louis Armstrong, and the Bridalplasty contestants into sirens.

There was a problem though. When you hook up external speakers to my TV, the TV assumes that the speakers will now control the volume and locks you out of its volume control.
IMG_7381
That would be fine if the speakers could recognize the volume up and volume down buttons on the TV remote, but this particular set of speakers is geared toward computers and has no IR input whatsoever. In place of an IR reciever it has a big standalone knob, which as an input device is pretty awesome, but you can’t use it remotely.

Turning it up to 12

So for months, I would set the volume to a middle level and only change it in dire situations. It’s only a foot away from the sofa, but it might as well be inside of Yucca Mountain.

This was all just a minor inconvenience, but I would think about the problem a lot. I wasn’t really trying to solve the issue, I was just using it as a brain teaser. I would try to come up with the most Goldbergian contraption I could. The funniest one was something that intercepts the volume signals from the normal TV remote and then turns a gear that turns the big knob in a direction that corresponds to which volume button you pressed. The conversion of mechanical signal (button press) to IR signal to mechanical signal (gear turning) to electrical signal (from the knob to the speakers) to audio signal (change in volume) seemed hilariously complicated.

Plan

It would have stayed just a funny idea if I hadn’t started playing around with an Arduino. Arduinos are tiny programmable microcontrollers that work really well with any sort of electronic component. They use the Processing language which is super simple and fun to code in. Making this crazy contraption was the perfect project to get me started with the Arduino. I ran down to the local Radio Shack and bought the two main components of the system: an IR reciever module and a Servo (servo being a fancy word for turny thing) and got started.

Getting the IR module to respond to my remote

Getting the IR module to work was not as hard as I thought it would be. I basically used an amazing blog post as the outline for my code. I added some Serial.print statements into the pulse_to_bits method so that I could see the codes that the receiver was getting from my particular remote. I ran the code with the Arduino IDE’s built-in Serial Monitor and was able to find the codes for both the Volume Up button and the Volume Down button on my Samsung remote.
Serial Monitor
The real bit sequence is actually much longer, but the length I collected seems to be enough to be basically unique. At least in my living room.

Getting the servo to turn the right amount in the right direction

Getting the servo turning took a little bit more work. Arduino has a Servo library called SoftwareServo and there’s another Arduino Servo library on Google Code, but neither of them worked with the servos I had lying around. So not knowing anything about servos, I did some extensive research and more than a little trial and error to come up with the following code.

void rotateServo(int servoPin, boolean clockwise, int duration)
{
  int pulse = clockwise?1200:1800;
  unsigned long endTime = millis() + duration;
  
  int i = 1;
  while(endTime > millis())
  {
    digitalWrite(servoPin,HIGH);
    delayMicroseconds(pulse); 
    digitalWrite(servoPin,LOW);
    delay(20); 
    i++;
  }
}

If you send the servo I was working with a stream of ones pulsed at 1200 milliseconds, it will turn clockwise. If you send the same digits but with an 1800ms pulse, it will turn counterclockwise. I sincerely hope someone else looking for this information finds this page, it will save them a ton of time.

Tying it all together

Once I had the IR receiver and the servo figured out, I made this circuit.
IMG_7394
Here is a video of it in action.

And this is the processing code that I uploaded to the Arduino.

#include <Servo.h>;
// 0.1 by pmalmsten
// 0.2 by farkinga
//This code is mostly verbatim from this blog post 
//http://goo.gl/R3Zm8 with the primary modifications
//being the addition of a servo and the magic bit strings
//that represent the volume-up and volume-down buttons 
//on my Samsung TV remote.


#define IR_BIT_LENGTH 32
#define BIT_1 1000          //Binary 1 threshold (Microseconds)
#define BIT_0 400           //Binary 0 threshold (Microseconds)
#define BIT_START 2000      //Start bit threshold (Microseconds)
#define DEBUG 1             //Serial connection must be started
                            //to debug

#define IR_PIN 0            //Sensor pin 1 wired through a 
                            //220 ohm resistor

#define LED_PIN 9           //"Ready to Recieve" flag, not
                            //needed but nice
#define POWER_PIN 11       // the red LED that indicates 
                           //if the power button is pressed.

#define SERVO_PIN 9

#define SERVO_INCREMENT 50

int runtime_debug = 0;
int output_key = 1;
int power_button = 0;

int servoPos = 0;

//IR Codes for my remote
int samsungVolumeUp[] = {1,1,1,0,0,0,0,0,1,1,1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,1};
int samsungVolumeDown[] = {1,1,1,0,0,0,0,0,1,1,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,1,0,1,1,1,1};

Servo volumeKnob;

void setup() {
  pinMode(IR_PIN, INPUT);
  
  volumeKnob.attach(SERVO_PIN);
  volumeKnob.write(servoPos);
  delay(100);
  volumeKnob.detach();
  
  Serial.begin(9600);
}

void loop() {

  int bitStream[IR_BIT_LENGTH];
  
  get_ir_key(bitStream);		    //Fetch the key
  do_response(bitStream);
  
  delay(200);
}

void do_response(int key[])
{
  if(intArraysAreEqual(key, samsungVolumeUp))
  {
    Serial.println("Smasung Volume UP!");
    rotateServo(SERVO_PIN, true, SERVO_INCREMENT);

  }
  else if(intArraysAreEqual(key, samsungVolumeDown))
  {
    Serial.println("Samsung Volume DOWN!");
    rotateServo(SERVO_PIN, false, SERVO_INCREMENT);

  }
  else
    Serial.println("Unrecognized Key.");
    
  
}

void read_pulse(int data[], int num_bits)
{
  for (int i = 0; i < num_bits; i++)
  {
    data[i] = pulseIn(IR_PIN, HIGH);
  }
}

void pulse_to_bits(int pulse[], int bits[], int num_bits)
{
  
  if (DEBUG || runtime_debug) { Serial.println("-----"); }

  for(int i = 0; i < num_bits ; i++)
  {
    //if (DEBUG || runtime_debug) { Serial.println(pulse[i]); }

    if(pulse[i] > BIT_1) //is it a 1?
    {
      bits[i] = 1;
    }
    else if(pulse[i] > BIT_0) //is it a 0?
    {
      bits[i] = 0;
    } 

    else //data is invalid...
    {
      Serial.println("Error");
    }
    Serial.print(bits[i]);
  }
  Serial.println();
  
}

void get_ir_key(int bits[])
{
  int pulse[IR_BIT_LENGTH];

  do {} //Wait for a start bit
  while(pulseIn(IR_PIN, LOW) < BIT_START);

  read_pulse(pulse, IR_BIT_LENGTH);

  pulse_to_bits(pulse, bits, IR_BIT_LENGTH);


  
}
void printBits(int bits[])
{
  for(int i = 0; i < IR_BIT_LENGTH; i++)
    Serial.print(bits[i]);
  Serial.println();
}
boolean intArraysAreEqual(int Array1[], int Array2[])
{ 
  
  for(int i = 0; i < IR_BIT_LENGTH; i++)
  {
    if(Array1[i] != Array2[i])
      return false;
  }
  
  return true;
}
void rotateServo(int servoPin, boolean clockwise, int duration)
{
  int pulse = clockwise?1200:1800;
  unsigned long endTime = millis() + duration;
  
  int i = 1;
  while(endTime > millis())
  {
    digitalWrite(servoPin,HIGH);
    delayMicroseconds(pulse); 
    digitalWrite(servoPin,LOW);
    delay(20); 
    i++;
  }
}

Leave a Reply

Your email address will not be published.