Skip to content

Instantly share code, notes, and snippets.

@samsp-msft
Created February 11, 2026 04:44
Show Gist options
  • Select an option

  • Save samsp-msft/a7d577d2645e8095478f71fc774ca938 to your computer and use it in GitHub Desktop.

Select an option

Save samsp-msft/a7d577d2645e8095478f71fc774ca938 to your computer and use it in GitHub Desktop.
Arduino code to convert B&O IR pulses & IC2 into serial comms
#include <Commands.h>
#include <pcf8574.h>
#include <Beomote.h>
#include <Commands.h>
#include <Wire.h>
PCF8574 PCF_20(0x20);
uint8_t lastButtons=0;
uint8_t mode=0;
void setup() {
// put your setup code here, to run once:
Serial.begin(57600);
Serial.println("initialized");
Beo.initialize(3); // I/O pin for the Eye IR input
}
void loop() {
// put your main code here, to run repeatedly:
BeoCommand cmd;
if (Beo.receive(cmd)) {
Serial.print(cmd.link);
Serial.print(":");
Serial.print(cmd.address);
Serial.print(":");
Serial.println(cmd.command);
}
else
{
uint8_t value = PCF_20.read8();
uint8_t buttons = value ^ 0xFF;
// Capture state, then act when buttons released
if (buttons !=0) {
lastButtons = buttons;
} else if (lastButtons != 0) {
Serial.print("0:254:");
Serial.println(lastButtons);
lastButtons=0;
}
}
}
/*********************************************************************************
B&O uses a PWM system, with 200ms pulses with specific sized gaps between them.
The gap size determines the data payload:
BEO_ZERO 3125
BEO_SAME 6250 - Repeat of the last Bit type
BEO_ONE 9375
BEO_STOP 12500 - Marks the start of a message
BEO_START 15625 - Marks the end of a message
Beo4 commmands are of the form:
START_PULSE
Link : bit
Address: Byte - Target device. 0=TV, 1=Audio, 1B = Lights
Command: Byte - see Commmands.h
STOP_PULSE
BeoRemote One uses an additional 4bits for commmands, sending the STOP mark after
21 bits of data
*********************************************************************************/
#include "Beomote.h"
Beomote Beo;
void Pin_ISR()
{
Beo.pulseISR();
}
ISR(TIMER1_OVF_vect)
{
Beo.timerISR();
}
// Pin needs to support interrupts, will depend on specific board being used.
// For example, Uno can use Pin2 or Pin3.
void Beomote::initialize(int pin)
{
irPin = pin;
// Setting the pinmode of the IR pin and resetting the listener
pinMode(irPin, INPUT);
reset();
// Clearing Control Register A
TCCR1A = 0;
// Setting the phase and frequency correct pwm, and stopping the timer
TCCR1B = _BV(WGM13);
long cycles = (F_CPU / 2000000) * TICK;
ICR1 = cycles;
TCCR1B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12));
// Setting the timer overflow interrupt enable bit
TIMSK1 = _BV(TOIE1);
// Resetting clock select register, and starts the clock with no prescale
TCCR1B |= _BV(CS10);
// Setup interrupt to listen to trailing edge of pulse
//attachInterrupt(digitalPinToInterrupt(pin), Pin_ISR, RISING); // TSOP
attachInterrupt(digitalPinToInterrupt(pin), Pin_ISR, FALLING); // B&O eye
}
void Beomote::reset()
{
index = -1;
state = WAITING;
timer = 0;
link = 0x00;
address = 0x00;
command = 0x00;
//Reset Timer
TCCR1B |= _BV(CS10);
}
int Beomote::receive(BeoCommand &cmd)
{
if (state == MSG_COMPLETE)
{
cmd.link = link;
//cmd.address = (beo_address)address;
//cmd.command = (beo_command)command;
cmd.address = address;
cmd.command = command;
reset();
return 1;
}
return 0;
}
int Beomote::fuzzyCompare(int value, int target)
{
int fudge = BEO_ZERO / 10;
return (value >= target - fudge) && (value <= target + fudge);
}
void Beomote::timerISR()
{
timer++;
}
void Beomote::pulseISR()
{
//int irData = !digitalRead(irPin);
//digitalWrite(13, irData);
int beoCode = 0;
int beoBit;
// Don't process new command if the old one hasn't been picked up
if (state == MSG_COMPLETE)
{
return;
}
if (state == WAITING)
{
timer = 0;
state = FIRST_PULSE;
}
else if (fuzzyCompare(timer, BEO_ZERO))
{
beoCode = BEO_ZERO;
beoBit = lastBeoBit = 0;
}
else if (fuzzyCompare(timer, BEO_SAME))
{
beoCode = BEO_SAME;
beoBit = lastBeoBit;
}
else if (fuzzyCompare(timer, BEO_ONE))
{
beoCode = BEO_ONE;
beoBit = lastBeoBit = 1;
}
else if (fuzzyCompare(timer, BEO_STOP))
{
beoCode = BEO_STOP;
}
else if (fuzzyCompare(timer, BEO_START))
{
beoCode = BEO_START;
}
else
{
// We haven't found a valid pulse size
//Serial.println("Invalid pulse");
//DumpState();
reset();
}
//We got a valid message, reset the timer and process it
timer = 0;
if (beoCode == BEO_START)
{
state = IN_PROGRESS;
index = 0;
}
else if (index == 0)
{
link = beoBit;
index++;
}
else if (index < 9)
{
address = address << 1;
address |= beoBit;
index++;
}
else if (beoCode == BEO_STOP)
{
state = MSG_COMPLETE;
}
else if (index < 21)
{
command = command << 1;
command |= beoBit;
index++;
}
else
{
#ifdef DEBUG
Serial.println("How did we get here?");
debugDumpState(beoCode);
#endif
reset();
}
}
void Beomote::debugDumpState(int beoCode)
{
Serial.println("Beomote State:");
Serial.print(" code:");
Serial.println(beoCode);
Serial.print(" state:");
Serial.println(state);
Serial.print(" timer:");
Serial.println(timer);
Serial.print(" index:");
Serial.println(index);
Serial.print(" link:");
Serial.println(link, HEX);
Serial.print(" address:");
Serial.println(address, HEX);
Serial.print(" command:");
Serial.println(command, HEX);
}
#ifndef _BEOMOTE_H
#define _BEOMOTE_H
#include "Commands.h"
#include <Arduino.h>
#include <avr/io.h>
#include <avr/interrupt.h>
// Common divisors for 3125 = 1, 5, 25, 125, 625, 3125
#define TICK 25
// Defining the Bang & Olufsen commands
#define BEO_ZERO (3125 / TICK) //125
#define BEO_SAME (6250 / TICK) //250
#define BEO_ONE (9375 / TICK) //375
#define BEO_STOP (12500 / TICK) //500
#define BEO_START (15625 / TICK) //625
typedef struct {
boolean link;
// beo_address address;
// beo_command command;
unsigned char address;
unsigned int command;
} BeoCommand;
typedef enum IrState
{
WAITING = 0x00,
FIRST_PULSE = 0x04,
IN_PROGRESS = 0x08,
MSG_COMPLETE = 0x10
} IrState;
class Beomote {
public:
void initialize(int pin);
int receive(BeoCommand &cmd);
// This method is only to be called by the interrupt service routine
void timerISR();
void pulseISR();
private:
int irPin;
int lastState;
int lastBeoBit;
IrState state;
boolean link;
unsigned char address;
unsigned int command;
volatile int timer;
int index;
void reset();
int fuzzyCompare(int value, int target);
void debugDumpState(int beoCode);
};
extern Beomote Beo;
#endif
#ifndef _COMMANDS_H
#define _COMMANDS_H
typedef enum beo_address
{
SOURCE_VIDEO = 0x00,
SOURCE_AUDIO = 0x01,
SOURCE_VIDEOTAPE = 0x05,
SOURCE_ALL = 0x0F,
SOURCE_SPDEMO = 0x1D,
SOURCE_LIGHT = 0x1B,
SOURCE_BEOVISION1 = 0xA0,
SOURCE_BEOSOUND =0xA5,
SOURCE_BEOVISION2= 0xA8,
SOURCE_BEOSOUND2=0xAD,
SOURCE_BEOVISION3 = 0xB0,
SOURCE_BEOSOUND3 =0xB5
} beo_address;
typedef enum beo_command
{
NUMBER_0 = 0x00,
NUMBER_1 = 0x01,
NUMBER_2 = 0x02,
NUMBER_3 = 0x03,
NUMBER_4 = 0x04,
NUMBER_5 = 0x05,
NUMBER_6 = 0x06,
NUMBER_7 = 0x07,
NUMBER_8 = 0x08,
NUMBER_9 = 0x09,
CLEAR = 0x0A,
STORE = 0x0B,
STANDBY = 0x0C,
MUTE = 0x0D,
INDEX = 0x0E,
RESET = 0x0E,
BACK = 0x14,
PICTURE_OFF = 0x1C,
P_UP = 0x1E,
P_DOWN = 0x1F,
TUNE = 0x20,
Counter=0x20,
CLOCK = 0x28,
FORMAT = 0x2A,
REWIND = 0x32,
RETURN = 0x33,
FORWARD = 0x34,
PLAY = 0x35,
GO = 0x35,
PAUSE = 0x36,
RECORD = 0x37,
GUIDE = 0x40,
SELECT = 0x3F,
INFO = 0x43,
SPEAKER = 0x44,
TURN = 0x46,
SOUND = 0x46,
SLEEP = 0x47,
LOUDNESS = 0x48,
PICTURE = 0x4A,
BASS = 0x4D,
TREBLE = 0x4E,
BALANCE = 0x4F,
LIST = 0x58,
MENU = 0x5C,
VOLUME_UP = 0x60,
VOLUME_DOWN = 0x64,
LEFT_REPEAT = 0x70,
RIGHT_REPEAT = 0x71,
UP_REPEAT = 0x72,
DOWN_REPEAT = 0x73,
GO_REPEAT = 0x75,
GREEN_REPEAT = 0x76,
YELLOW_REPEAT = 0x77,
BLUE_REPEAT = 0x78,
RED_REPEAT = 0x79,
EXIT = 0x7F,
TV = 0x80,
RADIO = 0x81,
VIDEO_AUX = 0x82,
AUDIO_AUX = 0x83,
HOME_MEDIA = 0x84, // Note uses address C0
VIDEO_TAPE = 0x85,
DVD = 0x86,
CAMCORD = 0x87,
TEXT = 0x88,
SP_DEMO = 0x89,
DIGITAL_TV = 0x8A,
PC = 0x8B,
WEB_MEDIA = 0x8C, // Note uses address C0
DOOR_CAM = 0x8D,
AUDIO_TAPE = 0x91,
CD = 0x92,
PHONO = 0x93,
NETRADIO = 0x93,
AUDIO_TAPE_2 = 0x94,
N_MUSIC = 0x94,
SPOTIFY = 0x96, // Note used address C0
CD2 = 0x97,
LIGHT = 0x9B,
MORNING = 0xF,
HOME =0x25,
DINNER =0x26,
CINEMA =0x27,
BED_TIME = 0x39,
NIGHT = 0x3A,
WELCOME_HOME = 0x3B,
GOODBYE = 0x3C,
CONTROL =0x9C,
WINDOW1 = 0xF,
CURTAIN1 = 0x1A,
ON_OFF1 = 0x25,
SHADE1 = 0x3B,
DIMENSION_2D = 0xAD,
DIMENSION_3D = 0xAE,
AV = 0xBF,
TRACKING = 0xC8,
HDMI1 = 0xCE, // Note uses address C0
HDMI2 = 0x1CE, // Note uses address C0
HDMI3 = 0x2CE, // Note uses address C0
HDMI4 = 0x3CE, // Note uses address C0
MATRIX1 = 0xCF, // Note uses address C0
MATRIX2 = 0x1CF, // Note uses address C0
CINEMA_ON = 0xDA,
CINEMA_OFF = 0xDB,
YELLOW = 0xD4,
GREEN = 0xD5,
BLUE = 0xD8,
RED = 0xD9,
HOME_CONTROL = 0xE3,
P_AND_P = 0xFA,
STAND = 0xF7,
YOUTUBE = 0x18C, // Note uses address C0
DEEZER = 0x196, // Note uses address C0
PATTERN_PLAY = 0x1D3, // Note uses address C0
BLUETOOTH = 0x283, // Note uses address C0
BLGW_APP = 0x28C, // Note uses address C0
Q_PLAY = 0x296, // Note uses address C0
} beo_command;
#endif
//
// FILE: PCF8574.cpp
// AUTHOR: Rob Tillaart
// DATE: 02-febr-2013
// VERSION: 0.1.02
// PURPOSE: I2C PCF8574 library for Arduino
// URL:
//
// HISTORY:
// 0.1.02 replaced ints with uint8_t to reduce footprint;
// added default value for shiftLeft() and shiftRight()
// renamed status() to lastError();
// 0.1.01 added value(); returns last read 8 bit value (cached);
// value() does not always reflect the latest state of the pins!
// 0.1.00 initial version
//
#include "PCF8574.h"
#include <Wire.h>
PCF8574::PCF8574(int address)
{
_address = address;
Wire.begin();
}
uint8_t PCF8574::read8()
{
Wire.beginTransmission(_address);
Wire.requestFrom(_address, 1);
#if (ARDUINO < 100)
_data = Wire.receive();
#else
_data = Wire.read();
#endif
_error = Wire.endTransmission();
return _data;
}
uint8_t PCF8574::value()
{
return _data;
}
void PCF8574::write8(uint8_t value)
{
Wire.beginTransmission(_address);
_data = value;
Wire.write(_data);
_error = Wire.endTransmission();
}
uint8_t PCF8574::read(uint8_t pin)
{
PCF8574::read8();
return (_data & (1<<pin)) > 0;
}
void PCF8574::write(uint8_t pin, uint8_t value)
{
PCF8574::read8();
if (value == LOW)
{
_data &= ~(1<<pin);
}
else
{
_data |= (1<<pin);
}
PCF8574::write8(_data);
}
void PCF8574::toggle(uint8_t pin)
{
PCF8574::read8();
_data ^= (1 << pin);
PCF8574::write8(_data);
}
void PCF8574::shiftRight(uint8_t n)
{
if (n == 0 || n > 7 ) return;
PCF8574::read8();
_data >>= n;
PCF8574::write8(_data);
}
void PCF8574::shiftLeft(uint8_t n)
{
if (n == 0 || n > 7) return;
PCF8574::read8();
_data <<= n;
PCF8574::write8(_data);
}
int PCF8574::lastError()
{
int e = _error;
_error = 0;
return e;
}
//
// END OF FILE
//
//
// FILE: PCF8574.H
// AUTHOR: Rob Tillaart
// DATE: 02-febr-2013
// VERSION: 0.1.02
// PURPOSE: I2C PCF8574 library for Arduino
// URL:
//
// HISTORY:
// see PCF8574.cpp file
//
#ifndef _PCF8574_H
#define _PCF8574_H
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#define PCF8574_LIB_VERSION "0.1.02"
class PCF8574
{
public:
PCF8574(int address);
uint8_t read8();
uint8_t read(uint8_t pin);
uint8_t value();
void write8(uint8_t value);
void write(uint8_t pin, uint8_t value);
void toggle(uint8_t pin);
void shiftRight(uint8_t n=1);
void shiftLeft(uint8_t n=1);
int lastError();
private:
int _address;
uint8_t _data;
int _error;
};
#endif
//
// END OF FILE
//
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment