Skip to content

Instantly share code, notes, and snippets.

@tomasen
Last active September 8, 2025 05:19
Show Gist options
  • Save tomasen/913694611507c334e991a72808124055 to your computer and use it in GitHub Desktop.
Save tomasen/913694611507c334e991a72808124055 to your computer and use it in GitHub Desktop.
IR bridge that maps Samsung TV Vol+/Vol−/Mute to Integra/Onkyo AVR commands using an Adafruit Metro Mini 328 V2 and IRremote.
// Metro Mini 328 V2 / Arduino Uno
// Wiring:
// PIN2 → TSOP38238 OUT
// GND → TSOP38238 GND
// 5V → TSOP38238 VCC
// 5V → 0.1µF capacitor → TSOP38238 VCC
// PIN3 → 1kΩ → PN2222 base
// GND → PN2222 emitter
// PN2222 collector → IR LED cathode (short lead)
// IR LED anode (long lead) → 50Ω → 5V
#include <IRremote.hpp>
const uint8_t IR_RECEIVE_PIN = 2; // TSOP38238 OUT
const uint8_t IR_SEND_PIN = 3; // -> 1k -> PN2222 base
const uint8_t LED_PIN = LED_BUILTIN; // On-board LED (D13 on Metro Mini/Uno)
// Samsung 32-bit raw values (as seen in ReceiveDump)
const uint32_t SAMSUNG_VOL_UP_RAW = 0xF8070707;
const uint32_t SAMSUNG_VOL_DOWN_RAW = 0xF40B0707;
const uint32_t SAMSUNG_MUTE = 0xF00F0707;
// Integra / Onkyo (NEC) address + commands
const uint16_t ONKYO_ADDR = 0x6DD2;
const uint8_t CMD_VOL_UP = 0x02;
const uint8_t CMD_VOL_DOWN = 0x03;
const uint8_t CMD_MUTE = 0x05;
// Rate limit so we don't spam the AVR (ms between transmissions).
// NEC frames are ~67 ms; 70–90 ms feels natural for volume ramp.
const uint16_t MIN_SEND_INTERVAL_MS = 800;
uint32_t lastSendMs = 0;
// Tiny, non-blocking LED pulse
inline void pulseLED(uint8_t ms = 10) {
digitalWrite(LED_PIN, HIGH);
delay(ms); // 10 ms is short; safe here
digitalWrite(LED_PIN, LOW);
}
inline bool canSendNow() {
uint32_t now = millis();
if (now - lastSendMs >= MIN_SEND_INTERVAL_MS) {
lastSendMs = now;
return true;
}
return false;
}
void setup() {
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// Start IR without library LED feedback; we control LED ourselves
IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);
IrSender.begin(IR_SEND_PIN);
IrSender.enableIROut(38); // NEC uses 38 kHz
Serial.begin(115200);
Serial.println(F("Samsung -> Integra volume translator with LED blinks"));
Serial.println(F("Blink 1x = received any IR; Blink + another 1x = matched & re-sent"));
}
void loop() {
// blinkOnce();
if (!IrReceiver.decode()) return;
Serial.println(F("Received Signal"));
// Copy out what we need, then resume ASAP to keep receiver ready
auto d = IrReceiver.decodedIRData;
bool isSamsung = (d.protocol == SAMSUNG);
uint32_t raw = d.decodedRawData;
bool held = d.flags & IRDATA_FLAGS_IS_REPEAT;
if (isSamsung) {
bool matched = true;
uint16_t command = 0;
uint8_t repeats = held ? 2 : 1; // ramp when holding
switch (raw) {
case SAMSUNG_VOL_UP_RAW:
// send Onkyo/Integra VOL+
command = CMD_VOL_UP;
Serial.println(F("Matched: SAMSUNG VOL+ -> Sent ONKYO VOL+"));
break;
case SAMSUNG_VOL_DOWN_RAW:
// send Onkyo/Integra VOL-
command = CMD_VOL_DOWN;
Serial.println(F("Matched: SAMSUNG VOL- -> Sent ONKYO VOL-"));
break;
case SAMSUNG_MUTE:
command = CMD_MUTE;
Serial.println(F("Matched: SAMSUNG MUTE -> Sent ONKYO MUTE"));
break;
default:
// Not a key we care about; ignore.
matched = false;
break;
}
if (matched && canSendNow()) {
// avoid IR interference
delay(100);
IrSender.sendNEC(ONKYO_ADDR, command, 0);
delay(200);
Serial.println(F("Signal Sent"));
}
}
IrReceiver.resume(); // free the receiver quickly
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment