diff --git a/README.md b/README.md index b598ffa..9b18f12 100644 --- a/README.md +++ b/README.md @@ -484,8 +484,8 @@ For sending, the **default software generated PWM has problems on AVR running wi ## Bang & Olufsen protocol The Bang & Olufsen protocol decoder is not enabled by default, i.e if no protocol is enabled explicitly by #define `DECODE_`. It must always be enabled explicitly by `#define DECODE_BEO`. This is because it has an **IR transmit frequency of 455 kHz** and therefore requires a different receiver hardware (TSOP7000).
-And because **generating a 455 kHz PWM signal is currently not implemented**, sending only works if `USE_NO_SEND_PWM` is defined, i.e. data is transferred by cable and not by IR!
-For more info, see [ir_BangOlufsen.hpp](https://github.com/Arduino-IRremote/Arduino-IRremote/blob/master/src/ir_BangOlufsen.hpp#L42). +And because **generating a 455 kHz PWM signal is currently only implemented for `SEND_PWM_BY_TIMER`**, sending only works if `SEND_PWM_BY_TIMER` or `USE_NO_SEND_PWM` is defined.
+For more info, see [ir_BangOlufsen.hpp](https://github.com/Arduino-IRremote/Arduino-IRremote/blob/master/src/ir_BangOlufsen.hpp#L44). # Handling unknown Protocols ## Disclaimer diff --git a/changelog.md b/changelog.md index 8b1515b..286e8be 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ See also the commit log at github: https://github.com/Arduino-IRremote/Arduino-I # 4.2.0 - The old decode function is renamed to decode_old(decode_results *aResults). decode (decode_results *aResults) is only available in IRremote.h and prints a message. - Added DECODE_ONKYO, to force 16 bit command and data decoding. +- Enable Bang&Olufsen 455 kHz if SEND_PWM_BY_TIMER is defined. ## 4.1.2 - Workaround for ESP32 RTOS delay() timing bug influencing the mark() function. diff --git a/src/IRSend.hpp b/src/IRSend.hpp index d88a8ee..195a47f 100644 --- a/src/IRSend.hpp +++ b/src/IRSend.hpp @@ -950,7 +950,7 @@ void IRsend::mark(uint16_t aMarkMicros) { */ enableSendPWMByTimer(); // Enable timer or ledcWrite() generated PWM output customDelayMicroseconds(aMarkMicros); - IRLedOff();// disables hardware PWM and manages feedback LED + IRLedOff(); // disables hardware PWM and manages feedback LED return; #elif defined(USE_NO_SEND_PWM) @@ -1167,15 +1167,15 @@ void IRsend::customDelayMicroseconds(unsigned long aMicroseconds) { */ void IRsend::enableIROut(uint_fast8_t aFrequencyKHz) { #if defined(SEND_PWM_BY_TIMER) - timerConfigForSend(aFrequencyKHz); // must set output pin mode and disable receive interrupt if required, e.g. uses the same resource + timerConfigForSend(aFrequencyKHz); // must set output pin mode and disable receive interrupt if required, e.g. uses the same resource #elif defined(USE_NO_SEND_PWM) - (void) aFrequencyKHz; + (void) aFrequencyKHz; #else periodTimeMicros = (1000U + (aFrequencyKHz / 2)) / aFrequencyKHz; // rounded value -> 26 for 38.46 kHz, 27 for 37.04 kHz, 25 for 40 kHz. # if defined(IR_SEND_PIN) - periodOnTimeMicros = (((periodTimeMicros * IR_SEND_DUTY_CYCLE_PERCENT) + 50) / 100U); // +50 for rounding -> 830/100 for 30% and 16 MHz + periodOnTimeMicros = (((periodTimeMicros * IR_SEND_DUTY_CYCLE_PERCENT) + 50) / 100U); // +50 for rounding -> 830/100 for 30% and 16 MHz # else // Heuristics! We require a nanosecond correction for "slow" digitalWrite() functions periodOnTimeMicros = (((periodTimeMicros * IR_SEND_DUTY_CYCLE_PERCENT) + 50 - (PULSE_CORRECTION_NANOS / 10)) / 100U); // +50 for rounding -> 530/100 for 30% and 16 MHz @@ -1184,9 +1184,9 @@ void IRsend::enableIROut(uint_fast8_t aFrequencyKHz) { #if defined(USE_OPEN_DRAIN_OUTPUT_FOR_SEND_PIN) && defined(OUTPUT_OPEN_DRAIN) // the mode INPUT for mimicking open drain is set at IRLedOff() # if defined(IR_SEND_PIN) - pinModeFast(IR_SEND_PIN, OUTPUT_OPEN_DRAIN); + pinModeFast(IR_SEND_PIN, OUTPUT_OPEN_DRAIN); # else - pinModeFast(sendPin, OUTPUT_OPEN_DRAIN); + pinModeFast(sendPin, OUTPUT_OPEN_DRAIN); # endif #else @@ -1194,7 +1194,7 @@ void IRsend::enableIROut(uint_fast8_t aFrequencyKHz) { // because ESP 2.0.2 ledcWrite does not work if pin mode is set, and RP2040 requires gpio_set_function(IR_SEND_PIN, GPIO_FUNC_PWM); # if defined(__AVR__) || !defined(SEND_PWM_BY_TIMER) # if defined(IR_SEND_PIN) - pinModeFast(IR_SEND_PIN, OUTPUT); + pinModeFast(IR_SEND_PIN, OUTPUT); # else pinModeFast(sendPin, OUTPUT); # endif @@ -1202,6 +1202,22 @@ void IRsend::enableIROut(uint_fast8_t aFrequencyKHz) { #endif // defined(USE_OPEN_DRAIN_OUTPUT_FOR_SEND_PIN) } +#if defined(SEND_PWM_BY_TIMER) +// Used for Bang&Olufsen +void IRsend::enableHighFrequencyIROut(uint_fast16_t aFrequencyKHz) { + timerConfigForSend(aFrequencyKHz); // must set output pin mode and disable receive interrupt if required, e.g. uses the same resource + // For Non AVR platforms pin mode for SEND_PWM_BY_TIMER must be handled by the timerConfigForSend() function + // because ESP 2.0.2 ledcWrite does not work if pin mode is set, and RP2040 requires gpio_set_function(IR_SEND_PIN, GPIO_FUNC_PWM); +# if defined(__AVR__) +# if defined(IR_SEND_PIN) + pinModeFast(IR_SEND_PIN, OUTPUT); +# else + pinModeFast(sendPin, OUTPUT); +# endif +# endif +} +#endif + uint16_t IRsend::getPulseCorrectionNanos() { return PULSE_CORRECTION_NANOS; } diff --git a/src/IRremoteInt.h b/src/IRremoteInt.h index 4d02cac..ed5c4f7 100644 --- a/src/IRremoteInt.h +++ b/src/IRremoteInt.h @@ -437,6 +437,9 @@ public: size_t write(decode_type_t aProtocol, uint16_t aAddress, uint16_t aCommand, int_fast8_t aNumberOfRepeats = NO_REPEATS); void enableIROut(uint_fast8_t aFrequencyKHz); +#if defined(SEND_PWM_BY_TIMER) + void enableHighFrequencyIROut(uint_fast16_t aFrequencyKHz); // Used for Bang&Olufsen +#endif void sendPulseDistanceWidthFromArray(uint_fast8_t aFrequencyKHz, uint16_t aHeaderMarkMicros, uint16_t aHeaderSpaceMicros, uint16_t aOneMarkMicros, uint16_t aOneSpaceMicros, uint16_t aZeroMarkMicros, uint16_t aZeroSpaceMicros, diff --git a/src/ir_BangOlufsen.hpp b/src/ir_BangOlufsen.hpp index ba356de..9bfd4b8 100644 --- a/src/ir_BangOlufsen.hpp +++ b/src/ir_BangOlufsen.hpp @@ -160,13 +160,17 @@ void IRsend::sendBangOlufsenDataLink(uint32_t aHeader, uint8_t aData, int_fast8_ * @param aBackToBack If true send data back to back, which cannot be decoded if ENABLE_BEO_WITHOUT_FRAME_GAP is NOT defined */ void IRsend::sendBangOlufsenRaw(uint32_t aRawData, int_fast8_t aBits, bool aBackToBack) { -#if defined(USE_NO_SEND_PWM) || BEO_KHZ == 38 // BEO_KHZ == 38 is for unit test which runs the B&O protocol with 38 kHz +#if defined(USE_NO_SEND_PWM) || defined(SEND_PWM_BY_TIMER) || BEO_KHZ == 38 // BEO_KHZ == 38 is for unit test which runs the B&O protocol with 38 kHz /* - * 455 kHz PWM is currently not supported, maximum is 180 kHz + * 455 kHz PWM is currently only supported with SEND_PWM_BY_TIMER defined, otherwise maximum is 180 kHz */ # if !defined(USE_NO_SEND_PWM) +# if defined(SEND_PWM_BY_TIMER) + enableHighFrequencyIROut (BEO_KHZ); +# elif (BEO_KHZ == 38) enableIROut (BEO_KHZ); // currently only for unit test +# endif # endif // AGC / Start - 3 bits + first constant 0 header bit described in the official documentation diff --git a/src/private/IRTimer.hpp b/src/private/IRTimer.hpp index e4f4522..4cced9a 100644 --- a/src/private/IRTimer.hpp +++ b/src/private/IRTimer.hpp @@ -50,7 +50,7 @@ void timerDisableReceiveInterrupt(); void timerConfigForReceive(); void enableSendPWMByTimer(); void disableSendPWMByTimer(); -void timerConfigForSend(uint8_t aFrequencyKHz); +void timerConfigForSend(uint16_t aFrequencyKHz); #if defined(SEND_PWM_BY_TIMER) && ( (defined(ESP32) || defined(ARDUINO_ARCH_RP2040) || defined(PARTICLE)) || defined(ARDUINO_ARCH_MBED) ) #define SEND_PWM_DOES_NOT_USE_RECEIVE_TIMER // Receive timer and send generation are independent, so it is recommended to always define SEND_PWM_BY_TIMER @@ -122,7 +122,7 @@ void timerDisableReceiveInterrupt() { * timerConfigForSend() is used exclusively by IRsend::enableIROut(). * @param aFrequencyKHz Frequency of the sent PWM signal in kHz. There is no practical reason to have a sub kHz resolution for sending frequency :-). */ -void timerConfigForSend(uint8_t aFrequencyKHz) { +void timerConfigForSend(uint16_t aFrequencyKHz) { } /** @@ -427,7 +427,7 @@ void disableSendPWMByTimer() { * timerConfigForSend() is used exclusively by IRsend::enableIROut() * Set output pin mode and disable receive interrupt if it uses the same resource */ -void timerConfigForSend(uint8_t aFrequencyKHz) { +void timerConfigForSend(uint16_t aFrequencyKHz) { timerDisableReceiveInterrupt(); # if (((F_CPU / 2000) / 38) < 256) @@ -517,14 +517,19 @@ void disableSendPWMByTimer() { * timerConfigForSend() is used exclusively by IRsend::enableIROut() * Set output pin mode and disable receive interrupt if it uses the same resource */ -void timerConfigForSend(uint8_t aFrequencyKHz) { +void timerConfigForSend(uint16_t aFrequencyKHz) { timerDisableReceiveInterrupt(); # if (((F_CPU / 2000) / 38) < 256) - const uint16_t tPWMWrapValue = (F_CPU / 2000) / (aFrequencyKHz); // 210,52 for 38 kHz @16 MHz clock, 2000 instead of 1000 because of Phase Correct PWM + /* + * tPWMWrapValue is 210.52 for 38 kHz, 17.58 for 455 kHz @16 MHz clock. + * 210 -> 38.095 kHz, 17 -> 470.588 kHz @16 MHz clock. + * We use 2000 instead of 1000 in the formula, because of Phase Correct PWM. + */ + const uint16_t tPWMWrapValue = (F_CPU / 2000) / (aFrequencyKHz); TCCR2A = _BV(WGM20); // PWM, Phase Correct, Top is OCR2A TCCR2B = _BV(WGM22) | _BV(CS20); // CS20 -> no prescaling - OCR2A = tPWMWrapValue - 1; // The top value for the timer. The modulation frequency will be F_CPU / 2 / OCR2A. + OCR2A = tPWMWrapValue - 1; // The top value for the timer. The modulation frequency will be F_CPU / 2 / (OCR2A + 1). OCR2B = ((tPWMWrapValue * IR_SEND_DUTY_CYCLE_PERCENT) / 100) - 1; TCNT2 = 0; // not really required, since we have an 8 bit counter, but makes the signal more reproducible # else @@ -586,7 +591,7 @@ void disableSendPWMByTimer() { * timerConfigForSend() is used exclusively by IRsend::enableIROut() * Set output pin mode and disable receive interrupt if it uses the same resource */ -void timerConfigForSend(uint8_t aFrequencyKHz) { +void timerConfigForSend(uint16_t aFrequencyKHz) { #if F_CPU > 16000000 #error "Creating timer PWM with timer 3 is not supported for F_CPU > 16 MHz" #endif @@ -637,7 +642,7 @@ void disableSendPWMByTimer() { TCCR4A &= ~(_BV(COM4A1)); } -void timerConfigForSend(uint8_t aFrequencyKHz) { +void timerConfigForSend(uint16_t aFrequencyKHz) { #if F_CPU > 16000000 #error "Creating timer PWM with timer 4 is not supported for F_CPU > 16 MHz" #endif @@ -706,7 +711,7 @@ void disableSendPWMByTimer() { * timerConfigForSend() is used exclusively by IRsend::enableIROut() * Set output pin mode and disable receive interrupt if it uses the same resource */ -void timerConfigForSend(uint8_t aFrequencyKHz) { +void timerConfigForSend(uint16_t aFrequencyKHz) { #if F_CPU > 16000000 #error "Creating timer PWM with timer 4 HS is not supported for F_CPU > 16 MHz" #endif @@ -767,7 +772,7 @@ void disableSendPWMByTimer() { * timerConfigForSend() is used exclusively by IRsend::enableIROut() * Set output pin mode and disable receive interrupt if it uses the same resource */ -void timerConfigForSend(uint8_t aFrequencyKHz) { +void timerConfigForSend(uint16_t aFrequencyKHz) { #if F_CPU > 16000000 #error "Creating timer PWM with timer 5 is not supported for F_CPU > 16 MHz" #endif @@ -826,7 +831,7 @@ void disableSendPWMByTimer() { * timerConfigForSend() is used exclusively by IRsend::enableIROut() * Set output pin mode and disable receive interrupt if it uses the same resource */ -void timerConfigForSend(uint8_t aFrequencyKHz) { +void timerConfigForSend(uint16_t aFrequencyKHz) { #if F_CPU > 16000000 #error "Creating timer PWM with timer TINY0 is not supported for F_CPU > 16 MHz" #endif @@ -885,7 +890,7 @@ void disableSendPWMByTimer() { * timerConfigForSend() is used exclusively by IRsend::enableIROut() * Set output pin mode and disable receive interrupt if it uses the same resource */ -void timerConfigForSend(uint8_t aFrequencyKHz) { +void timerConfigForSend(uint16_t aFrequencyKHz) { timerDisableReceiveInterrupt(); # if (((F_CPU / 1000) / 38) < 256) @@ -981,7 +986,7 @@ void disableSendPWMByTimer() { * timerConfigForSend() is used exclusively by IRsend::enableIROut() * Set output pin mode and disable receive interrupt if it uses the same resource */ -void timerConfigForSend(uint8_t aFrequencyKHz) { +void timerConfigForSend(uint16_t aFrequencyKHz) { #if F_CPU > 16000000 // we have only prescaler 2 or must take clock of timer A (which is non deterministic) #error "Creating timer PWM with timer TCB0 is not possible for F_CPU > 16 MHz" @@ -1050,7 +1055,7 @@ void disableSendPWMByTimer() { * timerConfigForSend() is used exclusively by IRsend::enableIROut() * Set output pin mode and disable receive interrupt if it uses the same resource */ -void timerConfigForSend(uint8_t aFrequencyKHz) { +void timerConfigForSend(uint16_t aFrequencyKHz) { timerDisableReceiveInterrupt(); const uint16_t tPWMWrapValue = (F_CPU / 1000) / (aFrequencyKHz); // 526,31 for 38 kHz @20 MHz clock @@ -1140,7 +1145,7 @@ void disableSendPWMByTimer() { * timerConfigForSend() is used exclusively by IRsend::enableIROut() * Set output pin mode and disable receive interrupt if it uses the same resource */ -void timerConfigForSend(uint8_t aFrequencyKHz) { +void timerConfigForSend(uint16_t aFrequencyKHz) { timerDisableReceiveInterrupt(); // TODO really required here? Do we have a common resource for Teensy3.0, 3.1 # if defined(IR_SEND_PIN) pinMode(IR_SEND_PIN, OUTPUT); @@ -1209,7 +1214,7 @@ void disableSendPWMByTimer() { * timerConfigForSend() is used exclusively by IRsend::enableIROut() * Set output pin mode and disable receive interrupt if it uses the same resource */ -void timerConfigForSend(uint8_t aFrequencyKHz) { +void timerConfigForSend(uint16_t aFrequencyKHz) { timerDisableReceiveInterrupt(); # if defined(IR_SEND_PIN) pinMode(IR_SEND_PIN, OUTPUT); @@ -1292,7 +1297,7 @@ void disableSendPWMByTimer() { * timerConfigForSend() is used exclusively by IRsend::enableIROut() * Set output pin mode and disable receive interrupt if it uses the same resource */ -void timerConfigForSend(uint8_t aFrequencyKHz) { +void timerConfigForSend(uint16_t aFrequencyKHz) { timerDisableReceiveInterrupt(); # if defined(IR_SEND_PIN) pinMode(IR_SEND_PIN, OUTPUT); @@ -1418,7 +1423,7 @@ void disableSendPWMByTimer() { * timerConfigForSend() is used exclusively by IRsend::enableIROut() * ledcWrite since ESP 2.0.2 does not work if pin mode is set. Disable receive interrupt if it uses the same resource */ -void timerConfigForSend(uint8_t aFrequencyKHz) { +void timerConfigForSend(uint16_t aFrequencyKHz) { ledcSetup(SEND_AND_RECEIVE_TIMER_LEDC_CHANNEL, aFrequencyKHz * 1000, 8); // 8 bit PWM resolution # if defined(IR_SEND_PIN) ledcAttachPin(IR_SEND_PIN, SEND_AND_RECEIVE_TIMER_LEDC_CHANNEL); // bind pin to channel @@ -1601,7 +1606,7 @@ void disableSendPWMByTimer() { * timerConfigForSend() is used exclusively by IRsend::enableIROut() * Set output pin mode and disable receive interrupt if it uses the same resource */ -void timerConfigForSend(uint8_t aFrequencyKHz) { +void timerConfigForSend(uint16_t aFrequencyKHz) { sPwmOutForSendPWM.period_us(1000 / aFrequencyKHz); // 26.315 for 38 kHz sIROutPuseWidth = (1000 * IR_SEND_DUTY_CYCLE_PERCENT) / (aFrequencyKHz * 100); } @@ -1662,7 +1667,7 @@ void disableSendPWMByTimer() { * timerConfigForSend() is used exclusively by IRsend::enableIROut() * Set output pin mode and disable receive interrupt if it uses the same resource */ -void timerConfigForSend(uint8_t aFrequencyKHz) { +void timerConfigForSend(uint16_t aFrequencyKHz) { # if defined(IR_SEND_PIN) gpio_set_function(IR_SEND_PIN, GPIO_FUNC_PWM); // Find out which PWM slice is connected to IR_SEND_PIN @@ -1864,7 +1869,7 @@ void disableSendPWMByTimer() { * timerConfigForSend() is used exclusively by IRsend::enableIROut() * Set output pin mode and disable receive interrupt if it uses the same resource */ -void timerConfigForSend(uint8_t aFrequencyKHz) { +void timerConfigForSend(uint16_t aFrequencyKHz) { timerDisableReceiveInterrupt(); # if defined(IR_SEND_PIN) pinMode(IR_SEND_PIN, OUTPUT); @@ -1901,7 +1906,7 @@ void enableSendPWMByTimer() { void disableSendPWMByTimer() { } -void timerConfigForSend(uint8_t aFrequencyKHz) { +void timerConfigForSend(uint16_t aFrequencyKHz) { timerDisableReceiveInterrupt(); # if defined(IR_SEND_PIN) pinMode(IR_SEND_PIN, OUTPUT);