Skip to content
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

Bang & Olufsen support plus higher precision and frequency PWM generation #2074

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/IRrecv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,10 @@ bool IRrecv::decode(decode_results *results, irparams_t *save,
DPRINTLN("Attempting York decode");
if (decodeYork(results, offset, kYorkBits)) return true;
#endif // DECODE_YORK
#if DECODE_BANG_OLUFSEN
DPRINTLN("Attempting Bang & Olufsen decode");
if (decodeBangOlufsen(results, offset)) return true;
#endif // DECODE_BANG_OLUFSEN
// Typically new protocols are added above this line.
}
#if DECODE_HASH
Expand Down
6 changes: 6 additions & 0 deletions src/IRrecv.h
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,12 @@ class IRrecv {
const uint16_t kYorkBits,
const bool strict = true);
#endif // DECODE_YORK
#if DECODE_BANG_OLUFSEN
bool decodeBangOlufsen(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kBangOlufsenBits,
const bool strict = false);
#endif // DECODE_BANG_OLUFSEN
};

#endif // IRRECV_H_
11 changes: 10 additions & 1 deletion src/IRremoteESP8266.h
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,13 @@
#define SEND_YORK _IR_ENABLE_DEFAULT_
#endif // SEND_YORK

#ifndef DECODE_BANG_OLUFSEN
#define DECODE_BANG_OLUFSEN _IR_ENABLE_DEFAULT_
#endif // DECODE_BANG_OLUFSEN
#ifndef SEND_BANG_OLUFSEN
#define SEND_BANG_OLUFSEN _IR_ENABLE_DEFAULT_
#endif // SEND_BANG_OLUFSEN

#if (DECODE_ARGO || DECODE_DAIKIN || DECODE_FUJITSU_AC || DECODE_GREE || \
DECODE_KELVINATOR || DECODE_MITSUBISHI_AC || DECODE_TOSHIBA_AC || \
DECODE_TROTEC || DECODE_HAIER_AC || DECODE_HITACHI_AC || \
Expand Down Expand Up @@ -1137,8 +1144,9 @@ enum decode_type_t {
WOWWEE,
CARRIER_AC84, // 125
YORK,
BANG_OLUFSEN,
// Add new entries before this one, and update it to point to the last entry.
kLastDecodeType = YORK,
kLastDecodeType = BANG_OLUFSEN,
};

// Message lengths & required repeat values
Expand Down Expand Up @@ -1435,6 +1443,7 @@ const uint16_t kRhossDefaultRepeat = 0;
const uint16_t kClimaButlerBits = 52;
const uint16_t kYorkBits = 136;
const uint16_t kYorkStateLength = 17;
const uint16_t kBangOlufsenBits = 16;


// Legacy defines. (Deprecated)
Expand Down
96 changes: 88 additions & 8 deletions src/IRsend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
/// i.e. If not, assume a 100% duty cycle. Ignore attempts to change the
/// duty cycle etc.
IRsend::IRsend(uint16_t IRsendPin, bool inverted, bool use_modulation)
: IRpin(IRsendPin), periodOffset(kPeriodOffset) {
: IRpin(IRsendPin) {
if (inverted) {
outputOn = LOW;
outputOff = HIGH;
Expand Down Expand Up @@ -68,15 +68,12 @@ void IRsend::ledOn() {
/// @param[in] use_offset Should we use the calculated offset or not?
/// @return nr. of uSeconds.
/// @note (T = 1/f)
uint32_t IRsend::calcUSecPeriod(uint32_t hz, bool use_offset) {
uint32_t IRsend::calcUSecPeriod(uint32_t hz) {
if (hz == 0) hz = 1; // Avoid Zero hz. Divide by Zero is nasty.
uint32_t period =
(1000000UL + hz / 2) / hz; // The equiv of round(1000000/hz).
// Apply the offset and ensure we don't result in a <= 0 value.
if (use_offset)
return std::max((uint32_t)1, period + periodOffset);
else
return std::max((uint32_t)1, period);
// Ensure we don't result in a <= 0 value.
return std::max((uint32_t)1, period);
}

/// Set the output frequency modulation and duty cycle.
Expand All @@ -101,11 +98,34 @@ void IRsend::enableIROut(uint32_t freq, uint8_t duty) {
#ifdef UNIT_TEST
_freq_unittest = freq;
#endif // UNIT_TEST

#ifndef UNIT_TEST
_fractionalBits = 14;

// Maximum signed value that fits.
uint32_t maxValue = 0x7FFF >> _fractionalBits;
uint32_t period = calcUSecPeriod(freq);

// Decrement the number of fractional bits until the period fits.
while (maxValue < period)
{
--_fractionalBits;
maxValue = 0x7FFF >> _fractionalBits;
}

uint32_t fixedPointPeriod = ((1000000ULL << _fractionalBits) + freq / 2) / freq;

// Nr. of uSeconds the LED will be on per pulse.
onTimePeriod = (fixedPointPeriod * _dutycycle) / kDutyMax;
// Nr. of uSeconds the LED will be off per pulse.
offTimePeriod = fixedPointPeriod - onTimePeriod;
#else
uint32_t period = calcUSecPeriod(freq);
// Nr. of uSeconds the LED will be on per pulse.
onTimePeriod = (period * _dutycycle) / kDutyMax;
// Nr. of uSeconds the LED will be off per pulse.
offTimePeriod = period - onTimePeriod;
#endif
}

#if ALLOW_DELAY_CALLS
Expand Down Expand Up @@ -166,6 +186,58 @@ uint16_t IRsend::mark(uint16_t usec) {
// Not simple, so do it assuming frequency modulation.
uint16_t counter = 0;
IRtimer usecTimer = IRtimer();
#ifndef UNIT_TEST
#if SEND_BANG_OLUFSEN && ESP8266 && F_CPU < 160000000L
// Free running loop to attempt to get close to the 455 kHz required by Bang & Olufsen.
// Define BANG_OLUFSEN_CHECK_MODULATION temporarily to test frequency and time.
// Runs at ~300 kHz on an 80 MHz ESP8266.
// This is far from ideal but works if the transmitter is close enough.
uint32_t periodUInt = (onTimePeriod + offTimePeriod) >> _fractionalBits;
periodUInt = std::max(uint32_t(1), periodUInt);
if (periodUInt <= 5) {
uint32_t nextCheck = usec / periodUInt / 2; // Assume we can at least run for this number of periods.
for (;;) { // nextStop is not updated in this loop.
ledOn();
ledOff();
counter++;
if (counter >= nextCheck) {
uint32_t now = usecTimer.elapsed();
int32_t timeLeft = usec - now;
if (timeLeft <= 1) {
return counter;
}
uint32_t periodsToEnd = counter * timeLeft / now;
// Check again when we are half way closer to the end.
nextCheck = (periodsToEnd >> 2) + counter;
}
}
}
#endif

// Use absolute time for zero drift (but slightly uneven period).
// Using IRtimer.elapsed() instead of _delayMicroseconds is better for short period times.
// Maxed out at ~190 kHz on an 80 MHz ESP8266.
// Maxed out at ~460 kHz on ESP32.
uint32_t nextStop = 0; // Must be 32 bits to not overflow when usec is near max.
while ((nextStop >> _fractionalBits) < usec) { // Loop until we've met/exceeded our required time.
ledOn();
nextStop += onTimePeriod;
uint32_t nextStopUInt = std::min(nextStop >> _fractionalBits, uint32_t(usec));
while(usecTimer.elapsed() < nextStopUInt);
ledOff();
counter++;
nextStop += offTimePeriod;
nextStopUInt = std::min(nextStop >> _fractionalBits, uint32_t(usec));
uint32_t now = usecTimer.elapsed();
int32_t delay = nextStopUInt - now;
if (delay > 0) {
while(usecTimer.elapsed() < nextStopUInt);
} else {
// This means we ran past nextStop and need to reset to actual time to avoid playing catch-up with a far too short period.
nextStop = (now << _fractionalBits) + (offTimePeriod >> 1);
}
}
#else
// Cache the time taken so far. This saves us calling time, and we can be
// assured that we can't have odd math problems. i.e. unsigned under/overflow.
uint32_t elapsed = usecTimer.elapsed();
Expand All @@ -184,6 +256,7 @@ uint16_t IRsend::mark(uint16_t usec) {
std::min(usec - elapsed - onTimePeriod, (uint32_t)offTimePeriod));
elapsed = usecTimer.elapsed(); // Update & recache the actual elapsed time.
}
#endif
return counter;
}

Expand All @@ -207,7 +280,7 @@ void IRsend::space(uint32_t time) {
int8_t IRsend::calibrate(uint16_t hz) {
if (hz < 1000) // Were we given kHz? Supports the old call usage.
hz *= 1000;
periodOffset = 0; // Turn off any existing offset while we calibrate.
int8_t periodOffset = 0;
enableIROut(hz);
IRtimer usecTimer = IRtimer(); // Start a timer *just* before we do the call.
uint16_t pulses = mark(UINT16_MAX); // Generate a PWM of 65,535 us. (Max.)
Expand Down Expand Up @@ -798,6 +871,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) {
return kXmpBits;
case YORK:
return kYorkBits;
case BANG_OLUFSEN:
return kBangOlufsenBits;
// No default amount of bits.
case FUJITSU_AC:
case MWM:
Expand Down Expand Up @@ -838,6 +913,11 @@ bool IRsend::send(const decode_type_t type, const uint64_t data,
sendArris(data, nbits, min_repeat);
break;
#endif // SEND_ARRIS
#if SEND_BANG_OLUFSEN
case BANG_OLUFSEN:
sendBangOlufsen(data, nbits, min_repeat);
break;
#endif // SEND_BANG_OLUFSEN
#if SEND_BOSE
case BOSE:
sendBose(data, nbits, min_repeat);
Expand Down
28 changes: 13 additions & 15 deletions src/IRsend.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,6 @@
// Constants
// Offset (in microseconds) to use in Period time calculations to account for
// code excution time in producing the software PWM signal.
#if defined(ESP32)
// Calculated on a generic ESP-WROOM-32 board with v3.2-18 SDK @ 240MHz
const int8_t kPeriodOffset = -2;
#elif (defined(ESP8266) && F_CPU == 160000000L) // NOLINT(whitespace/parens)
// Calculated on an ESP8266 NodeMCU v2 board using:
// v2.6.0 with v2.5.2 ESP core @ 160MHz
const int8_t kPeriodOffset = -2;
#else // (defined(ESP8266) && F_CPU == 160000000L)
// Calculated on ESP8266 Wemos D1 mini using v2.4.1 with v2.4.0 ESP core @ 40MHz
const int8_t kPeriodOffset = -5;
#endif // (defined(ESP8266) && F_CPU == 160000000L)
const uint8_t kDutyDefault = 50; // Percentage
const uint8_t kDutyMax = 100; // Percentage
// delayMicroseconds() is only accurate to 16383us.
Expand Down Expand Up @@ -885,6 +874,15 @@ class IRsend {
const uint16_t nbytes = kYorkStateLength,
const uint16_t repeat = kNoRepeat);
#endif // SEND_YORK
#if SEND_BANG_OLUFSEN
void sendBangOlufsen(const uint64_t data,
const uint16_t nbits = kBangOlufsenBits,
const uint16_t repeat = kNoRepeat);
void sendBangOlufsenRaw(const uint64_t rawData,
const uint16_t bits,
const bool datalink,
const bool backToBack);
#endif // SEND_BANG_OLUFSEN

protected:
#ifdef UNIT_TEST
Expand All @@ -905,13 +903,13 @@ class IRsend {
#else
uint32_t _freq_unittest;
#endif // UNIT_TEST
uint16_t onTimePeriod;
uint16_t offTimePeriod;
uint16_t onTimePeriod; // Fixed point.
uint16_t offTimePeriod; // Fixed point.
uint8_t _fractionalBits; // Number of fractional bits in on/offTimePeriod.
uint16_t IRpin;
int8_t periodOffset;
uint8_t _dutycycle;
bool modulation;
uint32_t calcUSecPeriod(uint32_t hz, bool use_offset = true);
uint32_t calcUSecPeriod(uint32_t hz);
#if SEND_SONY
void _sendSony(const uint64_t data, const uint16_t nbits,
const uint16_t repeat, const uint16_t freq);
Expand Down
2 changes: 2 additions & 0 deletions src/IRtext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,8 @@ IRTEXT_CONST_BLOB_DECL(kAllProtocolNamesStr) {
D_STR_CARRIER_AC84, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_YORK || SEND_YORK,
D_STR_YORK, D_STR_UNSUPPORTED) "\x0"
COND(DECODE_BANG_OLUFSEN || SEND_BANG_OLUFSEN,
D_STR_BANG_OLUFSEN, D_STR_UNSUPPORTED) "\x0"
///< New protocol (macro) strings should be added just above this line.
"\x0" ///< This string requires double null termination.
};
Expand Down
10 changes: 2 additions & 8 deletions src/IRtimer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ uint32_t IRtimer::elapsed() {
#else
uint32_t now = _IRtimer_unittest_now;
#endif
if (start <= now) // Check if the system timer has wrapped.
return now - start; // No wrap.
else
return UINT32_MAX - start + now; // Has wrapped.
return now - start; // Wrap safe.
}

/// Add time to the timer to simulate elapsed time.
Expand Down Expand Up @@ -64,10 +61,7 @@ uint32_t TimerMs::elapsed() {
#else
uint32_t now = _TimerMs_unittest_now;
#endif
if (start <= now) // Check if the system timer has wrapped.
return now - start; // No wrap.
else
return UINT32_MAX - start + now; // Has wrapped.
return now - start; // Wrap safe.
}

/// Add time to the timer to simulate elapsed time.
Expand Down
4 changes: 2 additions & 2 deletions src/IRutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,8 @@ String resultToSourceCode(const decode_results * const results) {
// "uint32_t address = 0xDEADBEEF;\n"
// "uint32_t command = 0xDEADBEEF;\n"
// "uint64_t data = 0xDEADBEEFDEADBEEF;" = ~116 chars max.
output.reserve(55 + (length * 7) + hasState ? 25 + (results->bits / 8) * 6
: 116);
output.reserve(55 + (length * 7) + (hasState ? 25 + (results->bits / 8) * 6
: 116));
// Start declaration
output += F("uint16_t "); // variable type
output += F("rawData["); // array name
Expand Down
2 changes: 1 addition & 1 deletion src/ir_Airwell.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const uint8_t kAirwellOverhead = 4;
const uint16_t kAirwellHalfClockPeriod = 950; // uSeconds
const uint16_t kAirwellHdrMark = 3 * kAirwellHalfClockPeriod; // uSeconds
const uint16_t kAirwellHdrSpace = 3 * kAirwellHalfClockPeriod; // uSeconds
const uint16_t kAirwellFooterMark = 5 * kAirwellHalfClockPeriod; // uSeconds
//const uint16_t kAirwellFooterMark = 5 * kAirwellHalfClockPeriod; // uSeconds

using irutils::addBoolToString;
using irutils::addModeToString;
Expand Down