Skip to content

Marantz-RC5 extension support #1314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ Available as [Arduino library "IRremote"](https://www.arduinolibraries.info/libr
# Supported IR Protocols
` NEC / Onkyo / Apple `     ` Denon / Sharp `     ` Panasonic / Kaseikyo `

` JVC `     ` LG `     ` RC5 `     ` RC6 `     ` Samsung `     ` Sony `
` JVC `     ` LG `     ` RC5 `     ` RC6 `     ` Samsung `     ` Sony `     ` Marantz `

` Universal Pulse Distance `     ` Universal Pulse Width `     ` Universal Pulse Distance Width`

Expand Down Expand Up @@ -131,6 +131,7 @@ Old `IrReceiver.decodedIRData.rawDataPtr->rawbuf` is now `IrReceiver.irparams.ra
- LED feedback is always enabled for sending. It can only be disabled by using `#define NO_LED_SEND_FEEDBACK_CODE` or `#define NO_LED_FEEDBACK_CODE`.
- The second parameter of the function `IrSender.begin(uint_fast8_t aSendPin, bool aEnableLEDFeedback)` has changed to `uint_fast8_t aFeedbackLEDPin`. Therefore this function call no longer works as expected because it uses the old boolean value of e.g. `ENABLE_LED_FEEDBACK` which evaluates to true or 1 as pin number of the LED feedback pin number.
- New experimental Velux send function.
- Send function for Marantz version of the RC5x protocol. This protocol has an additional 6 bit command extension and adds a short pause after the address is sent to identify this variant of the protocol.

## New features of version 4.x
- **Since 4.3 `IrSender.begin(DISABLE_LED_FEEDBACK)` will no longer work**, use `#define NO_LED_SEND_FEEDBACK_CODE` instead.
Expand Down
14 changes: 13 additions & 1 deletion examples/SendDemo/SendDemo.ino
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,12 @@ void setup() {
* Set up the data to be sent.
* For most protocols, the data is build up with a constant 8 (or 16 byte) address
* and a variable 8 bit command.
* There are exceptions like Sony and Denon, which have 5 bit address.
* There are exceptions like Sony, Denon or Marantz, which have 5 bit address.
* Furthermore, the extended protocol from Marantz has a 6 bit command extension.
*/
uint16_t sAddress = 0x0102;
uint8_t sCommand = 0x34;
uint8_t sCommandExtension = 0x0B; // Only used for Marantz extended protocol
uint16_t s16BitCommand = 0x5634;
uint8_t sRepeats = 0;

Expand Down Expand Up @@ -344,6 +346,16 @@ void loop() {
IrSender.sendSharp(sAddress & 0x1F, sCommand, sRepeats);
delay(DELAY_AFTER_SEND);

Serial.println(F("Send Marantz variant of RC5x with 6 command bits and additiaonal command extension"));
Serial.flush();
IrSender.sendRC5Marantz(sAddress & 0x1F, sCommand, sCommandExtension, sRepeats);
delay(DELAY_AFTER_SEND);

Serial.println(F("Send Marantz variant of RC5x with 7 command bits and additiaonal command extension"));
Serial.flush();
IrSender.sendRC5Marantz(sAddress & 0x1F, sCommand & 0x7F, sCommandExtension, sRepeats);
delay(DELAY_AFTER_SEND);

Serial.println(F("Send Sony/SIRCS with 7 command and 5 address bits"));
Serial.flush();
IrSender.sendSony(sAddress & 0x1F, sCommand & 0x7F, sRepeats);
Expand Down
31 changes: 24 additions & 7 deletions src/IRSend.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ void IRsend::setSendPin(uint_fast8_t aSendPin) {
/**
* Initializes the send and feedback pin
* @param aSendPin The Arduino pin number, where a IR sender diode is connected.
* @param aEnableLEDFeedback If true the feedback LED is activated while receiving or sending a PWM signal /a mark
* @param aFeedbackLEDPin If 0 / USE_DEFAULT_FEEDBACK_LED_PIN, then take board specific FEEDBACK_LED_ON() and FEEDBACK_LED_OFF() functions
*/
void IRsend::begin(uint_fast8_t aSendPin, uint_fast8_t aFeedbackLEDPin) {
Expand Down Expand Up @@ -1084,15 +1085,16 @@ void IRsend::sendPulseDistanceWidth_P(PulseDistanceWidthProtocolConstants const
* Sends Biphase (Manchester) coded data MSB first
* This function concatenates two marks to one longer mark,
* thus avoiding the programmatically pause between the generation of two separate marks.
* Always send start bit, do not send the trailing space of the start bit
* Send an additional start bit if specified, do not send the trailing space of the start bit
* 0 -> mark+space
* 1 -> space+mark
* The output always ends with a space
* can only send 31 bit data, since we put the start bit as 32th bit on front
* can only send 31 bit data, since we may put the start bit as 32th bit on front
* @param aData uint32 or uint64 holding the bits to be sent.
* @param aNumberOfBits Number of bits from aData to be actually sent.
* @param aSendStartBit if true sends an additional start bit with value 1 as MSB, if false no start bit is sent and data may start with 0 or 1.
*/
void IRsend::sendBiphaseData(uint16_t aBiphaseTimeUnit, uint32_t aData, uint_fast8_t aNumberOfBits) {
void IRsend::sendBiphaseData(uint16_t aBiphaseTimeUnit, uint32_t aData, uint_fast8_t aNumberOfBits, bool aSendStartBit) {

IR_TRACE_PRINT(F("0x"));
IR_TRACE_PRINT(aData, HEX);
Expand All @@ -1101,12 +1103,25 @@ void IRsend::sendBiphaseData(uint16_t aBiphaseTimeUnit, uint32_t aData, uint_fas
Serial.print('S');
#endif

uint32_t tMask;
uint_fast8_t tLastBitValue;
bool tNextBitIsOne;
// total number of bits to send including start bit if specified
uint8_t aBitsToSend = aNumberOfBits + (aSendStartBit ? 1 : 0);
// Data - Biphase code MSB first
if (aSendStartBit) {
// prepare for start with sending the start bit, which is 1
uint32_t tMask = 1UL << aNumberOfBits; // mask is now set for the virtual start bit
uint_fast8_t tLastBitValue = 1; // Start bit is a 1
bool tNextBitIsOne = 1; // Start bit is a 1
for (uint_fast8_t i = aNumberOfBits + 1; i > 0; i--) {
tMask = 1UL << aNumberOfBits; // mask is now set for the virtual start bit
tLastBitValue = 1; // Start bit is a 1
tNextBitIsOne = 1; // Start bit is a 1
} else {
// prepare to send only the data which may start with a 0 or 1 (e.g. after a defined pause or header when no additional start bit is needed)
tMask = 1UL << (aNumberOfBits - 1); // mask is now set for the MSB of data
tLastBitValue = ((aData & tMask) != 0) ? 1 : 0; // remember the value of the first bit to be sent
tNextBitIsOne = tLastBitValue;
}
// now send all bits
for (uint_fast8_t i = aBitsToSend; i > 0; i--) {
bool tCurrentBitIsOne = tNextBitIsOne;
tMask >>= 1;
tNextBitIsOne = ((aData & tMask) != 0) || (i == 1); // true for last bit to avoid extension of mark
Expand All @@ -1116,6 +1131,7 @@ void IRsend::sendBiphaseData(uint16_t aBiphaseTimeUnit, uint32_t aData, uint_fas
#endif
space(aBiphaseTimeUnit);
if (tNextBitIsOne) {
// if next bit is 1 send a single mark
mark(aBiphaseTimeUnit);
} else {
// if next bit is 0, extend the current mark in order to generate a continuous signal without short breaks
Expand All @@ -1128,6 +1144,7 @@ void IRsend::sendBiphaseData(uint16_t aBiphaseTimeUnit, uint32_t aData, uint_fas
Serial.print('0');
#endif
if (!tLastBitValue) {
// if last bit was 0 send a space
mark(aBiphaseTimeUnit);
}
space(aBiphaseTimeUnit);
Expand Down
4 changes: 3 additions & 1 deletion src/IRremoteInt.h
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ class IRsend {
__attribute__ ((deprecated ("Since version 4.1.0 parameter aSendStopBit is not longer required.")));
void sendPulseDistanceWidthData(uint16_t aOneMarkMicros, uint16_t aOneSpaceMicros, uint16_t aZeroMarkMicros,
uint16_t aZeroSpaceMicros, IRRawDataType aData, uint_fast8_t aNumberOfBits, uint8_t aFlags);
void sendBiphaseData(uint16_t aBiphaseTimeUnit, uint32_t aData, uint_fast8_t aNumberOfBits);
void sendBiphaseData(uint16_t aBiphaseTimeUnit, uint32_t aData, uint_fast8_t aNumberOfBits, bool aSendStartBit = true);

void mark(uint16_t aMarkMicros);
static void space(uint16_t aSpaceMicros);
Expand Down Expand Up @@ -645,6 +645,8 @@ class IRsend {
void sendKaseikyo_JVC(uint16_t aAddress, uint8_t aData, int_fast8_t aNumberOfRepeats); // LSB first

void sendRC5(uint8_t aAddress, uint8_t aCommand, int_fast8_t aNumberOfRepeats, bool aEnableAutomaticToggle = true);
void sendRC5Marantz(uint8_t aAddress, uint8_t aCommand, uint8_t aMarantzExtension, int_fast8_t aNumberOfRepeats,
bool aEnableAutomaticToggle = true);
void sendRC6(uint8_t aAddress, uint8_t aCommand, int_fast8_t aNumberOfRepeats, bool aEnableAutomaticToggle = true);
void sendRC6A(uint8_t aAddress, uint8_t aCommand, int_fast8_t aNumberOfRepeats, uint16_t aCustomer,
bool aEnableAutomaticToggle = true);
Expand Down
60 changes: 58 additions & 2 deletions src/ir_RC5_RC6.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ uint8_t sLastSendToggleValue = 1; // To start first command with toggle 0
//
#define RC5_ADDRESS_BITS 5
#define RC5_COMMAND_BITS 6
#define RC5_EXTENSION_BITS 6
#define RC5_COMMAND_FIELD_BIT 1
#define RC5_TOGGLE_BIT 1

Expand All @@ -99,15 +100,70 @@ uint8_t sLastSendToggleValue = 1; // To start first command with toggle 0
* Start of send and decode functions
************************************/

/**
* @param aCommand If aCommand is >=0x40 then we switch automatically to RC5X.
* @param aMarantzExtension 6 bit command extension which is sent after aCommand. aCommand and aMarantzExtension are sent after a short pause.
* @param aEnableAutomaticToggle Send toggle bit according to the state of the static sLastSendToggleValue variable.
*/
void IRsend::sendRC5Marantz(uint8_t aAddress, uint8_t aCommand, uint8_t aMarantzExtension, int_fast8_t aNumberOfRepeats,
bool aEnableAutomaticToggle) {

// Set IR carrier frequency
enableIROut (RC5_RC6_KHZ);

uint16_t tIRData = (aAddress & 0x1F);

if (aCommand < 0x40) {
// Auto discovery of RC5X, set field bit to 1
tIRData |= 1 << (RC5_TOGGLE_BIT + RC5_ADDRESS_BITS);
} else {
// Mask bit 7 of command and let field bit 0
aCommand &= 0x3F;
}
// Set the command to the 2nd partof data to be sent after the pause
uint16_t tIRExtData = (aCommand << RC5_EXTENSION_BITS);
// Set the Marantz command extension bits
tIRExtData |= (aMarantzExtension & 0x3F);

if (aEnableAutomaticToggle) {
if (sLastSendToggleValue == 0) {
sLastSendToggleValue = 1;
// set toggled bit
tIRData |= 1 << (RC5_ADDRESS_BITS);
} else {
sLastSendToggleValue = 0;
}
}

uint_fast8_t tNumberOfCommands = aNumberOfRepeats + 1;
while (tNumberOfCommands > 0) {

// start bit is sent by sendBiphaseData folllowed by the field bit and toggle bit and address
sendBiphaseData(RC5_UNIT, tIRData, RC5_COMMAND_FIELD_BIT + RC5_TOGGLE_BIT + RC5_ADDRESS_BITS);
// pause before the bits of command and command extension to indicate that it's Marantz-RC5x
space(4 * RC5_UNIT); // Marantz-RC5x has a pause before the bits of command and command extension
// send command and command extension
sendBiphaseData(RC5_UNIT, tIRExtData, RC5_COMMAND_BITS + RC5_EXTENSION_BITS, false);

tNumberOfCommands--;
// skip last delay!
if (tNumberOfCommands > 0) {
// send repeated command in a fixed raster
delay(RC5_REPEAT_DISTANCE / MICROS_IN_ONE_MILLI);
}
}
}

/**
* @param aCommand If aCommand is >=0x40 then we switch automatically to RC5X.
* @param aEnableAutomaticToggle Send toggle bit according to the state of the static sLastSendToggleValue variable.
*/
void IRsend::sendRC5(uint8_t aAddress, uint8_t aCommand, int_fast8_t aNumberOfRepeats, bool aEnableAutomaticToggle) {
// Set IR carrier frequency

// Set IR carrier frequency
enableIROut (RC5_RC6_KHZ);

uint16_t tIRData = ((aAddress & 0x1F) << RC5_COMMAND_BITS);
uint16_t tIRData = ((aAddress & 0x1F) << (RC5_COMMAND_BITS));

if (aCommand < 0x40) {
// Auto discovery of RC5X, set field bit to 1
Expand Down