TV Set Constant Volume Controller

Fabrizio Boco

September 2015

In this article by Fabizio Boco, author of  Arduino iOS Bluprints, we learn how to control a TV set volume using Arduino and iOS.

I don't watch TV much, but when I do, I usually completely relax and fall asleep. I know that TV is not meant for putting you off to sleep, but it does this to me. Unfortunately, commercials are transmitted at a very high volume and they wake me up. How can I relax if commercials wake me up every five minutes?

Can you believe it? During one of my naps between two commercials, I came up with a solution based on iOS and Arduino.

It's nothing complex. An iOS device listens to the TV set's audio, and when the audio level becomes higher than a preset threshold, the iOS device sends a message (via Bluetooth) to Arduino, which controls the TV set volume, emulating the traditional IR remote control. Exactly the same happens when the volume drops below another threshold. The final result is that the TV set volume is almost constant, independent of what is on the air. This helps me sleep longer!

The techniques that you are going to learn in this article are useful in many different ways. You can use an IR remote control for any purpose, or you can control many different devices, such as a CD/DVD player, a stereo set, Apple TV, a projector, and so on, directly from an Arduino and iOS device. As always, it is up to your imagination.

(For more resources related to this topic, see here.)

Constant Volume Controller requirements

Our aim is to design an Arduino-based device, which can make the TV set's volume almost constant by emulating the traditional remote controller, and an iOS application, which monitors the TV and decides when to decrease or increase the TV set's volume.

Hardware

Most TV sets can be controlled by an IR remote controller, which sends signals to control the volume, change the channel, and control all the other TV set functions.

IR remote controllers use a carrier signal (usually at 38 KHz) that is easy to isolate from noise and disturbances.

The carrier signal is turned on and off by following different rules (encoding) in order to transmit the 0 and 1 digital values.

The IR receiver removes the carrier signal (with a pass low filter) and decodes the remaining signal by returning a clear sequence of 0 and 1.

The IR remote control theory

You can find more information about the IR remote control at http://bit.ly/1UjhsIY.

Our circuit will emulate the IR remote controller by using an IR LED, which will send specific signals that can be interpreted by our TV set.

On the other hand, we can receive an IR signal with a phototransistor and decode it into an understandable sequence of numbers by designing a demodulator and a decoder.

Nowadays, electronics is very simple; an IR receiver module (Vishay 4938) will manage the complexity of signal demodulation, noise cancellation, triggering, and decoding. It can be directly connected to Arduino, making everything very easy.

In the project in this article, we need an IR receiver to discover the coding rules that are used by our own IR remote controller (and the TV set).

Additional electronic components

In this project, we need the following additional components:

  • IR LED Vishay TSAL6100
  • IR Receiver module Vishay TSOP 4838
  • Resistor 100Ω
  • Resistor 680Ω
  • Electrolytic capacitor 0.1μF

Electronic circuit

The following picture shows the electrical diagram of the circuit that we need for the project:

The IR receiver will be used only to capture the TV set's remote controller signals so that our circuit can emulate them.

However, an IR LED is constantly used to send commands to the TV set. The other two LEDs will show when Arduino increases or decreases the volume. They are optional and can be omitted.

As usual, the Bluetooth device is used to receive commands from the iOS device.

Powering the IR LED in the current limits of Arduino

From the datasheet of the TSAL6100, we know that the forward voltage is 1.35V. The voltage drop along R1 is then 5-1.35 = 3.65V, and the current provided by Arduino to power the LED is about 3.65/680=5.3 mA. The maximum current that is allowed for each PIN is 40 mA (the recommended value is 20 mA). So, we are within the limits. In case your TV set is far from the LED, you may need to reduce the R1 resistor in order to get more current (and the IR light). Use a new value of R1 in the previous calculations to check whether you are within the Arduino limits. For more information about the Arduino PIN current, check out http://bit.ly/1JosGac.

The following diagram shows how to mount the circuit on a breadboard:

Arduino code

The entire code of this project can be downloaded from https://www.packtpub.com/books/content/support.

To understand better the explanations in the following paragraphs, open the downloaded code while reading them.

In this project, we are going to use the IR remote library, which helps us code and decode IR signals.

The library can be downloaded from http://bit.ly/1Isd8Ay and installed by using the following procedure:

  1. Navigate to the release page of http://bit.ly/1Isd8Ay in order to get the latest release and download the IRremote.zip file.
  2. Unzip the file whatever you like.
  3. Open the Finder and then the Applications folder (Shift + Control + A).
  4. Locate the Arduino application.
  5. Right-click on it and select Show Package Contents.
  6. Locate the Java folder and then libraries.
  7. Copy the IRremote folder (unzipped in step 2) into the libraries folder.
  8. Restart Arduino if you have it running.

In this project, we need the following two Arduino programs:

  • One is used to acquire the codes that your IR remote controller sends to increase and decrease the volume
  • The other is the main program that Arduino has to run to automatically control the TV set volume

Let's start with the code that is used to acquire the IR remote controller codes.

Decoder setup code

In this section, we will be referring to the downloaded Decode.ino program that is used to discover the codes that are used by your remote controller.

Since the setup code is quite simple, it doesn't require a detailed explanation; it just initializes the library to receive and decode messages.

Decoder main program

In this section, we will be referring to the downloaded Decode.ino program; the main code receives signals from the TV remote controller and dumps the appropriate code, which will be included in the main program to emulate the remote controller itself.

Once the program is run, if you press any button on the remote controller, the console will show the following:

For IR Scope:
+4500 -4350 …

For Arduino sketch:
unsigned int raw[68] = {4500,4350,600,1650,600,1600,600,1600,…};

The second row is what we need. Please refer to the Testing and tuning section for a detailed description of how to use this data.

Now, we will take a look at the main code that will be running on Arduino all the time.

Setup code

In this section, we will be referring to the Arduino_VolumeController.ino program. The setup function initializes the nRF8001 board and configures the pins for the optional monitoring LEDs.

Main program

The loop function just calls the polACI function to allow the correct management of incoming messages from the nRF8001 board.

The program accepts the following two messages from the iOS device (refer to the rxCallback function):

  • D to decrease the volume
  • I to increase the volume

The following two functions perform the actual increasing and decreasing of volume by sending the two up and down buffers through the IR LED:

void volumeUp() {
irsend.sendRaw(up, VOLUME_UP_BUFFER_LEN, 38);
delay(20);
}

void volumeDown() {
irsend.sendRaw(down, VOLUME_DOWN_BUFFER_LEN, 38);
delay(20);
irsend.sendRaw(down, VOLUME_DOWN_BUFFER_LEN, 38);
delay(20);
}

The up and down buffers, VOLUME_UP_BUFFER_LEN and VOLUME_DOWN_BUFFER_LEN, are prepared with the help of the Decode.ino program (see the Testing and Tuning section).

iOS code

In this article, we are going to look at the iOS application that monitors the TV set volume and sends the volume down or volume up commands to the Arduino board in order to maintain the volume at the desired value.

The full code of this project can be downloaded from https://www.packtpub.com/books/content/support.

To understand better the explanations in the following paragraphs, open the downloaded code while reading them.

Create the Xcode project

We will create a new project as we already did previously. The following are the steps that you need to follow:

The following are the parameters for the new project:

  • Project Type: Tabbed application
  • Product Name: VolumeController
  • Language: Objective-C
  • Devices: Universal

To set a capability for this project, perform the following steps:

  1. Select the project in the left pane of Xcode.
  2. Select Capabilities in the right pane.
  3. Turn on the Background Modes option and select Audio and AirPlay (refer to the following picture). This allows an iOS device to listen to audio signals too when the iOS device screen goes off or the app goes in the background:

Since the structure of this project is very close to the Pet Door Locker, we can reuse a part of the user interface and the code by performing the following steps:

  1. Select FirstViewController.h and FirstViewController.m, right-click on them, click on Delete, and select Move to Trash.
  2. With the same procedure, deleteSecondViewControllerand Main.storyboard.
  3. Open the PetDoorLocker project in Xcode.
  4. Select the following files and drag and drop them to this project (refer to the following picture). BLEConnectionViewController.h

    •     BLEConnectionViewController.m
    •     Main.storyboard
      Ensure that Copy items if needed is selected and then click on Finish.
  5. Copy the icon that was used for the BLEConnectionViewController view controller.
  6. Create a new View Controller class and name it VolumeControllerViewController.
  7. Open the Main.storyboard and locate the main View Controller.
  8. Delete all the graphical components.
  9. Open the Identity Inspector and change the Class to VolumeControllerViewController.

Now, we are ready to create what we need for the new application.

Design the user interface for VolumeControllerViewController

This view controller is the main view controller of the application and contains just the following components:

  • The switch that turns on and off the volume control
  • The slider that sets the desired volume of the TV set

Once you have added the components and their layout constraints, you will end up with something that looks like the following screenshot:

Once the GUI components are linked with the code of the view controller, we end with the following code:

@interface VolumeControllerViewController ()

@property (strong, nonatomic) IBOutlet UISlider     *volumeSlider;

@end

and with:
- (IBAction)switchChanged:(UISwitch *)sender {
…
}
- (IBAction)volumeChanged:(UISlider *)sender {
…
}

Writing code for BLEConnectionViewController

Since we copied this View Controller from the Pet Door Locker project, we don't need to change it apart from replacing the key, which was used to store the peripheral UUID, from PetDoorLockerDevice to VolumeControllerDevice.

We saved some work!

Now, we are ready to work on the VolumeControllerViewController, which is much more interesting.

Writing code for VolumeControllerViewController

This is the main part of the application; almost everything happens here.

We need some properties, as follows:

@interface VolumeControllerViewController ()

@property (strong, nonatomic) IBOutlet UISlider *volumeSlider;

@property (strong, nonatomic) CBCentralManager   *centralManager;
@property (strong, nonatomic) CBPeripheral       *arduinoDevice;
@property (strong, nonatomic) CBCharacteristic   *sendCharacteristic;

@property (nonatomic,strong) AVAudioEngine       *audioEngine;

@property float                                 actualVolumeDb;
@property float                                 desiredVolumeDb;
@property float                                  desiredVolumeMinDb;
@property float                                 desiredVolumeMaxDb;

@property NSUInteger                             increaseVolumeDelay;

@end

Some are used to manage the Bluetooth communication and don't need much explanation. The audioEngine is the instance of AVAudioEngine, which allows us to transform the audio signal captured by the iOS device microphone in numeric samples. By analyzing these samples, we can obtain the power of the signal that is directly related to the TV set's volume (the higher the volume, the greater the signal power).

Analog-to-digital conversion

The operation of transforming an analog signal into a digital sequence of numbers, which represent the amplitude of the signal itself at different times, is called analog-to-digital conversion. Arduino analog inputs perform exactly the same operation. Together with the digital-to-analog conversion, it is a basic operation of digital signal processing and storing music in our devices and playing it with a reasonable quality. For more details, visit http://bit.ly/1N1QyXp.

The actualVolumeDb property stores the actual volume of the signal measured in dB (short for decibel).

Decibel (dB)

The decibel (dB) is a logarithmic unit that expresses the ratio between two values of a physical quantity. Referring to the power of a signal, its value in decibel is calculated with the following formula:

Here, P is the power of the signal and P0[PRK1]  is a reference power. You can find out more about decibel at http://bit.ly/1LZQM0m. We have to point out that if P < P0[PRK2] , the value of PdB[PRK3]  if lower of zero. So, decibel values are usually negative values, and 0dB indicates the maximum power of the signal.

The desiredVolumeDb property stores the desired volume measured in dB, and the user controls this value through the volume slider in the main tab of the app; desiredVolumeMinDb and desiredVolumeMaxDb are derived from the desiredVolumeDb.

The most significant part of the code is in the viewDidLoad method (refer to the downloaded code).

First, we instantiate the AudioEngine and get the default input node, which is the microphone, as follows:

 _audioEngine = [[AVAudioEngine alloc] init];
   AVAudioInputNode *input = [_audioEngine inputNode];

The AVAudioEngine is a very powerful class, which allows digital audio signal processing. We are just going to scratch its capabilities.

AVAudioEngine

You can find out more about AVAudioEngine by visiting http://apple.co/1kExe35 (AVAudioEngine in practice) and http://apple.co/1WYG6Tp.

The AVAudioEngine and other functions that we are going to use require that we add the following imports:

#import <AVFoundation/AVFoundation.h>
#import <Accelerate/Accelerate.h>

By installing an audio tap on the bus for our input node, we can get the numeric representation of the signal that the iOS device is listening to, as follows:

[input installTapOnBus:0 bufferSize:8192 format:[input inputFormatForBus:0] block:^(AVAudioPCMBuffer* buffer, AVAudioTime* when) {
…
…
}];

As soon as a new buffer of data is available, the code block is called and the data can be processed. Now, we can take a look at the code that transforms the audio data samples into actual commands to control the TV set:

for (UInt32 i = 0; i < buffer.audioBufferList->mNumberBuffers; i++) {

   Float32 *data = buffer.audioBufferList->mBuffers[i].mData;
   UInt32 numFrames = buffer.audioBufferList->mBuffers[i].mDataByteSize / sizeof(Float32);

// Squares all the data values
   vDSP_vsq(data, 1, data, 1, numFrames*buffer.audioBufferList->mNumberBuffers);

           // Mean value of the squared data values: power of the signal
   float meanVal = 0.0;
   vDSP_meanv(data, 1, &meanVal, numFrames*buffer.audioBufferList->mNumberBuffers);

   // Signal power in Decibel
   float meanValDb = 10 * log10(meanVal);

   _actualVolumeDb = _actualVolumeDb + 0.2*(meanValDb - _actualVolumeDb);

   if (fabsf(_actualVolumeDb) < _desiredVolumeMinDb && _centralManager.state == CBCentralManagerStatePoweredOn && _sendCharacteristic != nil) {

       //printf("Decrease volume\n");

       NSData* data=[@"D" dataUsingEncoding:NSUTF8StringEncoding];
       [_arduinoDevice writeValue:data forCharacteristic:_sendCharacteristic type:CBCharacteristicWriteWithoutResponse];

       _increaseVolumeDelay = 0;
   }

   if (fabsf(_actualVolumeDb) > _desiredVolumeMaxDb && _centralManager.state == CBCentralManagerStatePoweredOn && _sendCharacteristic != nil) {

       _increaseVolumeDelay++;
   }

   if (_increaseVolumeDelay > 10) {

       //printf("Increase volume\n");

       _increaseVolumeDelay = 0;

       NSData* data=[@"I" dataUsingEncoding:NSUTF8StringEncoding];
               [_arduinoDevice writeValue:data forCharacteristic:_sendCharacteristic type:CBCharacteristicWriteWithoutResponse];
           }
       }

In our case, the for cycle is executed just once because we have just one buffer and we are using only one channel.

The power of a signal, represented by N samples, can be calculated by using the following formula:

Here, v is the value of the nth signal sample.

Because the power calculation has to performed in real time, we are going to use the following functions, which are provided by the Accelerated Framework:

  • vDSP_vsq: This function calculates the square of each input vector element
  • vDSP_meanv: This function calculates the mean value of the input vector elements

The Accelerated Framework

The Accelerated Framework is an essential tool that is used for digital signal processing. It saves you time in implementing the most used algorithms and mostly providing implementation of algorithms that are optimized in terms of memory footprint and performance. More information on the Accelerated Framework can be found at http://apple.co/1PYIKE8 and http://apple.co/1JCJWYh.

Eventually, the signal power is stored in _actualVolumeDb. When the modulus of _actualVolumeDb is lower than the _desiredVolumeMinDb, the TV set's volume is too high, and we need to send a message to Arduino to reduce it. Don't forget that _actualVolumeDb is a negative number; the modulus decreases this number when the TV set's volume increases. Conversely, when the TV set's volume decreases, the _actualVolumeDb modulus increases, and when it gets higher than _desiredVolumeMaxDb, we need to send a message to Arduino to increase the TV set's volume.

During pauses in dialogues, the power of the signal tends to decrease even if the volume of the speech is not changed. Without any adjustment, the increasing and decreasing messages are continuously sent to the TV set during dialogues. To avoid this misbehavior, we send the volume increase message. Only after this does the signal power stay over the threshold for some time (when _increaseVolumeDelay is greater than 10).

We can take a look at the other view controller methods that are not complex.

When the view belonging at the view controller appears, the following method is called:

-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated]; NSError* error = nil; [self connect]; _actualVolumeDb = 0; [_audioEngine startAndReturnError:&error]; if (error) { NSLog(@"Error %@",[error description]); } }

In this function, we connect to the Arduino board and start the audio engine in order to start listening to the TV set.

When the view disappears from the screen, the viewDidDisappearmethod is called, and we disconnect from the Arduino and stop the audio engine, as follows:

-(void)viewDidDisappear:(BOOL)animated {

     [self viewDidDisappear:animated];

   [self disconnect];

   [_audioEngine pause];
}

The method that is called when the switch is operated (switchChanged) is pretty simple:

- (IBAction)switchChanged:(UISwitch *)sender {

   NSError* error = nil;

   if (sender.on) {
       [_audioEngine startAndReturnError:&error];

       if (error) {
           NSLog(@"Error %@",[error description]);
       }
       _volumeSlider.enabled = YES;
   }
   else {
       [_audioEngine stop];
       _volumeSlider.enabled = NO;
   }
}

The method that is called when the volume slider changes is as follows:

- (IBAction)volumeChanged:(UISlider *)sender {

   _desiredVolumeDb = 50.*(1-sender.value);
   _desiredVolumeMaxDb = _desiredVolumeDb + 2;
   _desiredVolumeMinDb = _desiredVolumeDb - 3;
}

We just set the desired volume and the lower and upper thresholds.

The other methods that are used to manage the Bluetooth connection and data transfer don't require any explanation, because they are exactly like in the previous projects.

Testing and tuning

We are now ready to test our new amazing system and spend more and more time watching TV (or taking more and more naps!) Let's perform the following procedure:

  1. Load the Decoder.ino sketch and open the Arduino IDE console.
  2. Point your TV remote controller to the TSOP4838 receiver and press the button that increases the volume. You should see something like the following appearing on the console:
    For IR Scope:
    +4500 -4350 …
    
    For Arduino sketch:
    unsigned int raw[68] = {4500,4350,600,1650,600,1600,600,1600,…};
  3. Copy all the values between the curly braces.
  4. Open the Arduino_VolumeController.ino and paste the values for the following:
    unsigned int up[68] = {9000, 4450, …..,};
  5. Check whether the length of the two vectors (68 in the example) is the same and modify it, if needed.
  6. Point your TV remote controller to the TSOP4838 receiver and press the button that decreases the volume. Copy the values and paste them for:
    unsigned int down[68] = {9000, 4400, ….,};
  7. Check whether the length of the two vectors (68 in the example) is the same and modify it, if needed.
  8. Upload the Arduino_VolumeController.ino to Arduino and point the IR LED towards the TV set.
  9. Open the iOS application, scan for the nRF8001, and then go to the main tab.
  10. Tap on connect and then set the desired volume by touching the slider.
  11. Now, you should see the blue LED and the green LED flashing. The TV set's volume should stabilize to the desired value.

To check whether everything is properly working, increase the volume of the TV set by using the remote control; you should immediately see the blue LED flashing and the volume getting lower to the preset value. Similarly, by decreasing the volume with the remote control, you should see the green LED flashing and the TV set's volume increasing.

Take a nap, and the commercials will not wake you up!

How to go further

The following are some improvements that can be implemented in this project:

  1. Changing channels and controlling other TV set functions.
  2. Catching handclaps to turn on or off the TV set.
  3. Adding a button to mute the TV set.
  4. Muting the TV set on receiving a phone call.

Anyway, you can use the IR techniques that you have learned for many other purposes. Take a look at the other functions provided by the IRremote library to learn the other provided options. You can find all the available functions in the IRremote.h that is stored in the IRremote library folder.

On the iOS side, try to experiment with the AV Audio Engine and the Accelerate Framework that is used to process signals.

Summary

This artcle focused on an easy but useful project and taught you how to use IR to transmit and receive data to and from Arduino. There are many different applications of the basic circuits and programs that you learned here.

On the iOS platform, you learned the very basics of capturing sounds from the device microphone and the DSP (digital signal processing). This allows you to leverage the processing capabilities of the iOS platform to expand your Arduino projects.

Resources for Article:


Further resources on this subject:


You've been reading an excerpt of:

Arduino iOS Blueprints

Explore Title
comments powered by Disqus