Enable Bang&Olufsen 455 kHz if SEND_PWM_BY_TIMER is defined.

This commit is contained in:
Armin 2023-03-29 13:28:06 +02:00
parent a73851300a
commit c8c7110ca0
6 changed files with 62 additions and 33 deletions

View File

@ -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_<XYZ>`. 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).<br/>
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!<br/>
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.<br/>
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

View File

@ -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.

View File

@ -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;
}

View File

@ -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,

View File

@ -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

View File

@ -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);