TV Sound Booster | |
May 2018
TV Sound Booster - with IR Remote Learning Function![]() In common with a lot of modern flat screen TVs, the sound quality on my Samsung TV is quite poor but I couldn't find a sound bar that would stand under the TV without obscuring the bottom centimetre or two of the screen and a sound base was too big. In any event, both suffered from severe buffering; sometimes the sound would lag behind the picture and sometimes it would stop altogether in an attempt to catch up so I decided to build a simple sound "booster" or "enhancer" of my own. I tested a few Bluetooth power amplifier modules but nearly all of them insisted on making a verbal announcement whenever they re-connected with the TV. As the connection was broken every time the TV was switched to standby, it was clear that it would very soon become an annoyance. Also, none of the modules I tested had provision to adjust the volume. So I tested the TV's optical output option using a simple,inexpensive Optical (TOSLINK) to RCA converter. I wanted to be able to adjust the volume using the existing TV Infra Red remote but, as both the optical and the Bluetooth outputs from the TV are at a fixed volume, a pre-amplifier with some sort of IR "learning" capability was required. The ability to adjust the treble, bass and balance would be an advantage - even if these were just used during the setup procedure. I wanted a smallish unit that would sit under the TV (on top of the DVR) and considered that 8 to 10 watts per channel would be sufficient - especially if it was used "in tandem" with the existing TV sound. After some research and experimentation on breadboards, the following circuit evolved:
The Circuit![]() Optical audio from the TV is converted to a standard RCA Line In signal using the extenal Optical to RCA converter - widely available on Amazon and eBay for around 5 UK pounds. I decided to keep the converter external to the amplifier and provide a 5v DC output from the amplifier to power the converter.
TDA1524AThe TDA1524A stereo preamplifier IC is ideal for this application: Volume, Treble, Bass and Balance are all adjustable using a 0 - 5v DC voltage instead of messing around with dual-gang stereo potentiometers. As I anticipated that the tone and balance controls would be used infrequently, I used simple preset potentiometers with thumb adjusters on the front panel for these functions - wired exactly as shown in the datasheet.
ATmega328PThe 0 - 5 volts for the volume control is, instead, derived from a PWM output from theATmega328P. As the default PWM frequency is around 490Hz, even with some filtering, it would be difficult to remove this from the audio signal so the Arduino Sketch sets the PWM frequency on pin 15 (Output D9) to 31.3kHz - well outside the audible range so simple filtering can be used with a 2k7 resistor and 47uF capacitor. The resulting 0 to 5v signal is applied to the TDA1524A's Volume control pin: pin 1.
During the learning process, a 0.96" OLED I2C display connected to ATmega328P pins A4 and A5, prompts the user which button on the remote the Sketch is waiting for the code for. Additionally, it lights up for 1.5 seconds to show the volume setting whenever the volume is adjusted and when the sound is un-muted. The 5v Vcc supply to the OLED is provided by ATmega328P output A3 (configured as a digital output). This allows the display to be switched off when it's not actually required in order to save a small amount of power and to avoid distraction when watching the TV. During initial testing on a breadboard, the OLED caused some interference on the audio when it was powered on. Although this was another reason to power the OLED down when it wasn't required, there is actually no sign of interference with the project properly built with decent earthing on a PCB.
TPA3110I used a Sure TPA3110 2x8W module for the power amplifier as it is widely available from many sources and I wasn't looking for a huge output. As I found it was possible for the output from the TDA1524A to easily overload the power amp's inputs, I used a couple of 47k preset potentiomenters to limit the signal level. They also act as a course balance adjustment.I used a small "buck" regulator for the 5v DC supply to the ATmega328P, the OLED display (through an ATmega328P output) and the IR Sensor. Unlike a standard 7805 regulator, the OKI-78SR-5 wastes virtually no power and runs cold. A USB A socket is fitted on the PCB to provide a 5v supply to the external Optical-to-RCA Converter. The TDA1524A pre-amp and the TPA3110 Power amp are connected directly to the 12 - 15 volt DC supply.
The Printed Circuit Board![]()
Download artwork for PCB Wizard Download artwork in PDF format
Construction![]()
Required Additional Arduino LibrariesAdafruit_GFX.hAdafruit_SSD1306.h IR Remote Library
Arduino Sketch#include <IRremote.h> #include <EEPROM.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> // Adafruit 0.96" OLED Library #define oledAddress 0x3c // Banggood oleds I2C address differs from Adafruit oleds. int SDIO = A4; // I2C bus for OLED Display. int SCLK = A5; Adafruit_SSD1306 oled(12); // Initialise Adafruit library int oledVcc = A3; // 5v supply for OLED int RECV_PIN = 2; // Infra Red input to ATmega328 IRrecv irrecv(RECV_PIN); // Initialise IR library decode_results results; long RemoteVolUp; // My remote's code = 0xe0e0e01f; long RemoteVolDown; // = 0xe0e0d02f; long RemoteMute; // = 0xe0e0f00f; long lastCode = 0; unsigned long timer = 0; unsigned long timerLed = 0; unsigned long volumeTimer = 0; bool muteLedOn = true; bool volumeTimerOn = false; int PWM = 9; // Frequency increased to 31372.55 Hz in setup. int learn = 8; int VolUp = 7; // Volume Up button. int VolDown = 6; // Volume down button int mutedLed = 5; // Flash LED when sound is muted int Volume = 255; int volumeOffset = 40; // 0 to 39 is virtually silent so make range 40 - 100. bool isMuted = false; void setup (void) { TCCR1B = (TCCR1B & 0b11111000) | 0x01; // Set PWM frequency on pins 9 & 10 to 31372.55 Hz Serial.begin(9600); pinMode(oledVcc, OUTPUT); // 5V supply to OLED display pinMode(mutedLed, OUTPUT); // Flash an LED while FM62429 volume is muted. pinMode(VolDown, INPUT_PULLUP); // Push buttons for Volume Up, Volume Down and pinMode(VolUp, INPUT_PULLUP); pinMode(learn, INPUT_PULLUP); // Learn remote codes. irrecv.enableIRIn(); // Start the receiver RemoteVolUp = EEPROMReadlong(1); // Read remote codes that have previously RemoteVolDown = EEPROMReadlong(5); // been saved in EEPROM. Each code is RemoteMute = EEPROMReadlong(9); // 4 bytes in length. if (EEPROM.read(0) <= 255) { // Read last volume setting from EEPROM. Volume = EEPROM.read(0); } analogWrite(PWM, Volume); // Set volume . } void startOled() { // OLED causes some interference to digitalWrite(oledVcc, HIGH); // the audio so it is only powered oled.begin(SSD1306_SWITCHCAPVCC, oledAddress); // up and initialised when required. delay(50); oled.clearDisplay(); oled.setTextColor(WHITE); } void learnRemote() { // Routine to receive remote codes for startOled(); // Volume Up, Volume Down & Mute and oled.setTextSize(1); // save them in EEPROM. oled.setCursor(40,0); oled.print("Learn"); oled.setCursor(5,8); oled.print("Vol + "); oled.display(); results.value = 0; while (results.value == 0) { if (irrecv.decode(&results)) { //Serial.println(results.value, HEX); irrecv.resume(); // Receive the next value delay(100); } } RemoteVolUp = results.value; oled.print("OK"); oled.setCursor(5, 16); oled.print("Vol - "); oled.display(); results.value = 0; while ((results.value == 0) || (results.value == RemoteVolUp)) { if (irrecv.decode(&results)) { // Serial.println(results.value, HEX); irrecv.resume(); // Receive the next value delay(100); } } RemoteVolDown = results.value; oled.print("OK"); oled.setCursor(5, 24); oled.print("Mute "); oled.display(); results.value = 0; while ((results.value == 0) || (results.value == RemoteVolDown)) { if (irrecv.decode(&results)) { //Serial.println(results.value, HEX); irrecv.resume(); // Receive the next value delay(100); } } RemoteMute = results.value; oled.print(" OK.. "); oled.print("Done "); oled.display(); if (EEPROMReadlong(1) != RemoteVolUp) EEPROMWritelong(1, RemoteVolUp); if (EEPROMReadlong(5) != RemoteVolDown) EEPROMWritelong(5, RemoteVolDown); if (EEPROMReadlong(9) != RemoteMute) EEPROMWritelong(9, RemoteMute); delay(3000); digitalWrite(oledVcc, LOW); lastCode = RemoteMute; isMuted = false; analogWrite(PWM, Volume); } void loop (void) { if (digitalRead(learn) == LOW) { while (digitalRead(learn) == LOW); learnRemote(); delay(100); } results.value = 0; if (irrecv.decode(&results)) { Serial.println(results.value, HEX); irrecv.resume(); // Receive the next value delay(80); } if ((digitalRead(VolUp) == LOW) || (results.value == RemoteVolUp)) { if (Volume < 255) { Volume++; analogWrite(PWM, Volume); isMuted = false; lastCode = results.value; } showVolume(); if (digitalRead(VolUp) == LOW) delay(100); } if ((digitalRead(VolDown) == LOW) || (results.value == RemoteVolDown)) { if (Volume > volumeOffset) { Volume--; analogWrite(PWM, Volume); isMuted = false; lastCode = results.value; } showVolume(); if (digitalRead(VolDown) == LOW) delay(100); } if ((results.value == RemoteMute) && (lastCode != RemoteMute)){ timer = millis(); if (!isMuted) { // The same IR code is used to mute and analogWrite(PWM, 0); // unmute. Remote may send the code isMuted = true; // more than once for one button- timerLed = millis(); // press so ignore 2nd and subsequent } else { // codes. Start a timer so that the analogWrite(PWM, Volume); // 'ignore' flag can be reset one showVolume(); // second after no more 'mute' codes isMuted = false; // are received. } // Next time the code is received must lastCode = results.value; // mean 'toggle' the mute setting. } if (millis() - timer > 1000) { // Reset the 'ignore mute code' flag. lastCode = 0; } if (millis() - timerLed > 250) { // Set up flashing output for muteLedOn = !muteLedOn; // 'muted' LED timerLed = millis(); } digitalWrite(mutedLed, (muteLedOn && isMuted)); // Flash 'muted' LED if muted. if ((millis() - volumeTimer > 1500) && volumeTimerOn) { // Turn off OLED display after volumeTimerOn = false; // 1.5 seconds and write new digitalWrite(oledVcc, LOW); // volume setting to EEPROM. if (EEPROM.read(0) != Volume) { EEPROM.write(0, Volume); } } } void showVolume() { if (!volumeTimerOn) { // If OLED is not already powered, startOled(); // power it up. oled.setTextSize(3); } oled.clearDisplay(); oled.setCursor(40,8); oled.print(map(Volume, volumeOffset, 255, 0, 100)); // 40 to 255 for Pre-amp IC = 0 to 100 for display oled.display(); volumeTimerOn = true; // Flag that the display timer is running volumeTimer = millis(); // Start the display timer. } void EEPROMWritelong(int address, long value) { //Decomposition from a long to 4 bytes by using bitshift. //One = Most significant -> Four = Least significant byte byte four = (value & 0xFF); byte three = ((value >> 8) & 0xFF); byte two = ((value >> 16) & 0xFF); byte one = ((value >> 24) & 0xFF); //Write the 4 bytes into the eeprom memory. EEPROM.write(address, four); EEPROM.write(address + 1, three); EEPROM.write(address + 2, two); EEPROM.write(address + 3, one); } long EEPROMReadlong(long address) { //Read the 4 bytes from the eeprom memory. long four = EEPROM.read(address); long three = EEPROM.read(address + 1); long two = EEPROM.read(address + 2); long one = EEPROM.read(address + 3); //Return the recomposed long by using bitshift. return ((four << 0) & 0xFF) + ((three << 8) & 0xFFFF) + ((two << 16) & 0xFFFFFF) + ((one << 24) & 0xFFFFFFFF); }
|