/* * IRSend.hpp * * Contains common functions for sending * * This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote. * ************************************************************************************ * MIT License * * Copyright (c) 2009-2023 Ken Shirriff, Rafi Khan, Armin Joachimsmeyer * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is furnished * to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ************************************************************************************ */ #ifndef _IR_SEND_HPP #define _IR_SEND_HPP #if defined(DEBUG) && !defined(LOCAL_DEBUG) #define LOCAL_DEBUG #else //#define LOCAL_DEBUG // This enables debug output only for this file #endif #if defined(TRACE) && !defined(LOCAL_TRACE) #define LOCAL_TRACE #else //#define LOCAL_TRACE // This enables debug output only for this file #endif /* * Low level hardware timing measurement */ //#define _IR_MEASURE_TIMING // for mark() //#define _IR_TIMING_TEST_PIN 7 // "pinModeFast(_IR_TIMING_TEST_PIN, OUTPUT);" is executed at begin() // /* * This improves readability of code by avoiding a lot of #if defined clauses */ #if defined(IR_SEND_PIN) #define sendPin IR_SEND_PIN #endif /** \addtogroup Sending Sending IR data for multiple protocols * @{ */ // The sender instance IRsend IrSender; IRsend::IRsend() { // @suppress("Class members should be properly initialized") #if !defined(IR_SEND_PIN) sendPin = 0; #endif #if !defined(NO_LED_FEEDBACK_CODE) setLEDFeedback(0, DO_NOT_ENABLE_LED_FEEDBACK); #endif } #if defined(IR_SEND_PIN) /** * Only required to set LED feedback * Simple start with defaults - LED feedback enabled! Used if IR_SEND_PIN is defined. Saves program memory. */ void IRsend::begin(){ # if !defined(NO_LED_FEEDBACK_CODE) setLEDFeedback(USE_DEFAULT_FEEDBACK_LED_PIN, LED_FEEDBACK_ENABLED_FOR_SEND); # endif #if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN) pinModeFast(_IR_TIMING_TEST_PIN, OUTPUT); #endif } /** * Only required to set LED feedback * @param aEnableLEDFeedback If true / ENABLE_LED_FEEDBACK, 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(bool aEnableLEDFeedback, uint_fast8_t aFeedbackLEDPin) { #if !defined(NO_LED_FEEDBACK_CODE) uint_fast8_t tEnableLEDFeedback = DO_NOT_ENABLE_LED_FEEDBACK; if(aEnableLEDFeedback) { tEnableLEDFeedback = LED_FEEDBACK_ENABLED_FOR_SEND; } setLEDFeedback(aFeedbackLEDPin, tEnableLEDFeedback); #else (void) aEnableLEDFeedback; (void) aFeedbackLEDPin; #endif } #else // defined(IR_SEND_PIN) IRsend::IRsend(uint_fast8_t aSendPin) { // @suppress("Class members should be properly initialized") sendPin = aSendPin; # if !defined(NO_LED_FEEDBACK_CODE) setLEDFeedback(0, DO_NOT_ENABLE_LED_FEEDBACK); # endif } /** * Initializes the send pin and enable LED feedback with board specific FEEDBACK_LED_ON() and FEEDBACK_LED_OFF() functions * @param aSendPin The Arduino pin number, where a IR sender diode is connected. */ void IRsend::begin(uint_fast8_t aSendPin) { sendPin = aSendPin; # if !defined(NO_LED_FEEDBACK_CODE) setLEDFeedback(USE_DEFAULT_FEEDBACK_LED_PIN, LED_FEEDBACK_ENABLED_FOR_SEND); # endif } void IRsend::setSendPin(uint_fast8_t aSendPin) { sendPin = 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, bool aEnableLEDFeedback, uint_fast8_t aFeedbackLEDPin) { #if defined(IR_SEND_PIN) (void) aSendPin; // for backwards compatibility #else sendPin = aSendPin; #endif #if !defined(NO_LED_FEEDBACK_CODE) uint_fast8_t tEnableLEDFeedback = DO_NOT_ENABLE_LED_FEEDBACK; if (aEnableLEDFeedback) { tEnableLEDFeedback = LED_FEEDBACK_ENABLED_FOR_SEND; } setLEDFeedback(aFeedbackLEDPin, tEnableLEDFeedback); #else (void) aEnableLEDFeedback; (void) aFeedbackLEDPin; #endif } #endif // defined(IR_SEND_PIN) /** * Interprets and sends a IRData structure. * @param aIRSendData The values of protocol, address, command and repeat flag are taken for sending. * @param aNumberOfRepeats Number of repeats to send after the initial data if data is no repeat. * @return 1 if data sent, 0 if no data sent (i.e. for BANG_OLUFSEN, which is currently not supported here) */ /** * Interprets and sends a IRData structure. * @param aIRSendData The values of protocol, address, command and repeat flag are taken for sending. * @param aNumberOfRepeats Number of repeats to send after the initial data if data is no repeat. * @return 1 if data sent, 0 if no data sent (i.e. for BANG_OLUFSEN, which is currently not supported here) */ size_t IRsend::write(IRData *aIRSendData, int_fast8_t aNumberOfRepeats) { auto tProtocol = aIRSendData->protocol; auto tAddress = aIRSendData->address; auto tCommand = aIRSendData->command; bool tIsRepeat = (aIRSendData->flags & IRDATA_FLAGS_IS_REPEAT); if (tIsRepeat) { aNumberOfRepeats = -1; // if aNumberOfRepeats < 0 then only a special repeat frame will be sent } // switch (tProtocol) { // 26 bytes bigger than if, else if, else // case NEC: // sendNEC(tAddress, tCommand, aNumberOfRepeats, tSendRepeat); // break; // case SAMSUNG: // sendSamsung(tAddress, tCommand, aNumberOfRepeats); // break; // case SONY: // sendSony(tAddress, tCommand, aNumberOfRepeats, aIRSendData->numberOfBits); // break; // case PANASONIC: // sendPanasonic(tAddress, tCommand, aNumberOfRepeats); // break; // case DENON: // sendDenon(tAddress, tCommand, aNumberOfRepeats); // break; // case SHARP: // sendSharp(tAddress, tCommand, aNumberOfRepeats); // break; // case JVC: // sendJVC((uint8_t) tAddress, (uint8_t) tCommand, aNumberOfRepeats); // casts are required to specify the right function // break; // case RC5: // sendRC5(tAddress, tCommand, aNumberOfRepeats, !tSendRepeat); // No toggle for repeats // break; // case RC6: // // No toggle for repeats// sendRC6(tAddress, tCommand, aNumberOfRepeats, !tSendRepeat); // No toggle for repeats // break; // default: // break; // } /* * Order of protocols is in guessed relevance :-) */ if (tProtocol == NEC) { sendNEC(tAddress, tCommand, aNumberOfRepeats); } else if (tProtocol == SAMSUNG) { sendSamsung(tAddress, tCommand, aNumberOfRepeats); } else if (tProtocol == SAMSUNG48) { sendSamsung48(tAddress, tCommand, aNumberOfRepeats); } else if (tProtocol == SAMSUNGLG) { sendSamsungLG(tAddress, tCommand, aNumberOfRepeats); } else if (tProtocol == SONY) { sendSony(tAddress, tCommand, aNumberOfRepeats, aIRSendData->numberOfBits); } else if (tProtocol == PANASONIC) { sendPanasonic(tAddress, tCommand, aNumberOfRepeats); } else if (tProtocol == DENON) { sendDenon(tAddress, tCommand, aNumberOfRepeats); } else if (tProtocol == SHARP) { sendSharp(tAddress, tCommand, aNumberOfRepeats); } else if (tProtocol == LG) { sendLG(tAddress, tCommand, aNumberOfRepeats); } else if (tProtocol == JVC) { sendJVC((uint8_t) tAddress, (uint8_t) tCommand, aNumberOfRepeats); // casts are required to specify the right function } else if (tProtocol == RC5) { sendRC5(tAddress, tCommand, aNumberOfRepeats, !tIsRepeat); // No toggle for repeats } else if (tProtocol == RC6) { sendRC6(tAddress, tCommand, aNumberOfRepeats, !tIsRepeat); // No toggle for repeats } else if (tProtocol == KASEIKYO_JVC) { sendKaseikyo_JVC(tAddress, tCommand, aNumberOfRepeats); } else if (tProtocol == KASEIKYO_DENON) { sendKaseikyo_Denon(tAddress, tCommand, aNumberOfRepeats); } else if (tProtocol == KASEIKYO_SHARP) { sendKaseikyo_Sharp(tAddress, tCommand, aNumberOfRepeats); } else if (tProtocol == KASEIKYO_MITSUBISHI) { sendKaseikyo_Mitsubishi(tAddress, tCommand, aNumberOfRepeats); } else if (tProtocol == NEC2) { sendNEC2(tAddress, tCommand, aNumberOfRepeats); } else if (tProtocol == ONKYO) { sendOnkyo(tAddress, tCommand, aNumberOfRepeats); } else if (tProtocol == APPLE) { sendApple(tAddress, tCommand, aNumberOfRepeats); #if !defined(EXCLUDE_EXOTIC_PROTOCOLS) } else if (tProtocol == BOSEWAVE) { sendBoseWave(tCommand, aNumberOfRepeats); } else if (tProtocol == MAGIQUEST) { // we have a 32 bit ID/address sendMagiQuest(aIRSendData->decodedRawData, tCommand); } else if (tProtocol == FAST) { // We have only 8 bit command sendFAST(tCommand, aNumberOfRepeats); } else if (tProtocol == LEGO_PF) { sendLegoPowerFunctions(tAddress, tCommand, tCommand >> 4, tIsRepeat); // send 5 autorepeats #endif } else { return 0; // Not supported by write. E.g for BANG_OLUFSEN } return 1; } /** * Simple version of write without support for MAGIQUEST and numberOfBits for SONY protocol * @param aNumberOfRepeats If aNumberOfRepeats < 0 then only a special repeat frame without leading and trailing space * will be sent by calling NECProtocolConstants.SpecialSendRepeatFunction(). */ size_t IRsend::write(decode_type_t aProtocol, uint16_t aAddress, uint16_t aCommand, int_fast8_t aNumberOfRepeats) { // switch (aProtocol) { // 26 bytes bigger than if, else if, else // case NEC: // sendNEC(aAddress, aCommand, aNumberOfRepeats, tSendRepeat); // break; // case SAMSUNG: // sendSamsung(aAddress, aCommand, aNumberOfRepeats); // break; // case SONY: // sendSony(aAddress, aCommand, aNumberOfRepeats, aIRSendData->numberOfBits); // break; // case PANASONIC: // sendPanasonic(aAddress, aCommand, aNumberOfRepeats); // break; // case DENON: // sendDenon(aAddress, aCommand, aNumberOfRepeats); // break; // case SHARP: // sendSharp(aAddress, aCommand, aNumberOfRepeats); // break; // case JVC: // sendJVC((uint8_t) aAddress, (uint8_t) aCommand, aNumberOfRepeats); // casts are required to specify the right function // break; // case RC5: // sendRC5(aAddress, aCommand, aNumberOfRepeats, !tSendRepeat); // No toggle for repeats // break; // case RC6: // // No toggle for repeats// sendRC6(aAddress, aCommand, aNumberOfRepeats, !tSendRepeat); // No toggle for repeats // break; // default: // break; // } /* * Order of protocols is in guessed relevance :-) */ if (aProtocol == NEC) { sendNEC(aAddress, aCommand, aNumberOfRepeats); } else if (aProtocol == SAMSUNG) { sendSamsung(aAddress, aCommand, aNumberOfRepeats); } else if (aProtocol == SAMSUNG48) { sendSamsung48(aAddress, aCommand, aNumberOfRepeats); } else if (aProtocol == SAMSUNGLG) { sendSamsungLG(aAddress, aCommand, aNumberOfRepeats); } else if (aProtocol == SONY) { sendSony(aAddress, aCommand, aNumberOfRepeats, SIRCS_12_PROTOCOL); } else if (aProtocol == PANASONIC) { sendPanasonic(aAddress, aCommand, aNumberOfRepeats); } else if (aProtocol == DENON) { sendDenon(aAddress, aCommand, aNumberOfRepeats); } else if (aProtocol == SHARP) { sendSharp(aAddress, aCommand, aNumberOfRepeats); } else if (aProtocol == LG) { sendLG(aAddress, aCommand, aNumberOfRepeats); } else if (aProtocol == JVC) { sendJVC((uint8_t) aAddress, (uint8_t) aCommand, aNumberOfRepeats); // casts are required to specify the right function } else if (aProtocol == RC5) { sendRC5(aAddress, aCommand, aNumberOfRepeats, (aNumberOfRepeats > 0)); // No toggle for repeats } else if (aProtocol == RC6) { sendRC6(aAddress, aCommand, aNumberOfRepeats, (aNumberOfRepeats > 0)); // No toggle for repeats } else if (aProtocol == KASEIKYO_JVC) { sendKaseikyo_JVC(aAddress, aCommand, aNumberOfRepeats); } else if (aProtocol == KASEIKYO_DENON) { sendKaseikyo_Denon(aAddress, aCommand, aNumberOfRepeats); } else if (aProtocol == KASEIKYO_SHARP) { sendKaseikyo_Sharp(aAddress, aCommand, aNumberOfRepeats); } else if (aProtocol == KASEIKYO_MITSUBISHI) { sendKaseikyo_Mitsubishi(aAddress, aCommand, aNumberOfRepeats); } else if (aProtocol == NEC2) { sendNEC2(aAddress, aCommand, aNumberOfRepeats); } else if (aProtocol == ONKYO) { sendOnkyo(aAddress, aCommand, aNumberOfRepeats); } else if (aProtocol == APPLE) { sendApple(aAddress, aCommand, aNumberOfRepeats); #if !defined(EXCLUDE_EXOTIC_PROTOCOLS) } else if (aProtocol == BOSEWAVE) { sendBoseWave(aCommand, aNumberOfRepeats); } else if (aProtocol == FAST) { // We have only 8 bit command sendFAST(aCommand, aNumberOfRepeats); } else if (aProtocol == LEGO_PF) { sendLegoPowerFunctions(aAddress, aCommand, aCommand >> 4, (aNumberOfRepeats < 0)); // send 5 autorepeats, except for dedicated repeats #endif } else { return 0; // Not supported by write. E.g for BANG_OLUFSEN } return 1; } /** * Function using an 16 byte microsecond timing array for every purpose. * Raw data starts with a Mark. No leading space as in received timing data! */ void IRsend::sendRaw(const uint16_t aBufferWithMicroseconds[], uint_fast16_t aLengthOfBuffer, uint_fast8_t aIRFrequencyKilohertz) { // Set IR carrier frequency enableIROut(aIRFrequencyKilohertz); /* * Raw data starts with a mark. */ for (uint_fast16_t i = 0; i < aLengthOfBuffer; i++) { if (i & 1) { // Odd space(aBufferWithMicroseconds[i]); } else { mark(aBufferWithMicroseconds[i]); } } } /** * Function using an 8 byte tick timing array to save program memory * Raw data starts with a Mark. No leading space as in received timing data! */ void IRsend::sendRaw(const uint8_t aBufferWithTicks[], uint_fast16_t aLengthOfBuffer, uint_fast8_t aIRFrequencyKilohertz) { // Set IR carrier frequency enableIROut(aIRFrequencyKilohertz); for (uint_fast16_t i = 0; i < aLengthOfBuffer; i++) { if (i & 1) { // Odd space(aBufferWithTicks[i] * MICROS_PER_TICK); } else { mark(aBufferWithTicks[i] * MICROS_PER_TICK); } } IRLedOff(); // Always end with the LED off } /** * Function using an 16 byte microsecond timing array in FLASH for every purpose. * Raw data starts with a Mark. No leading space as in received timing data! */ void IRsend::sendRaw_P(const uint16_t aBufferWithMicroseconds[], uint_fast16_t aLengthOfBuffer, uint_fast8_t aIRFrequencyKilohertz) { #if !defined(__AVR__) sendRaw(aBufferWithMicroseconds, aLengthOfBuffer, aIRFrequencyKilohertz); // Let the function work for non AVR platforms #else // Set IR carrier frequency enableIROut(aIRFrequencyKilohertz); /* * Raw data starts with a mark */ for (uint_fast16_t i = 0; i < aLengthOfBuffer; i++) { auto duration = pgm_read_word(&aBufferWithMicroseconds[i]); if (i & 1) { // Odd space(duration); # if defined(LOCAL_DEBUG) Serial.print(F("S=")); # endif } else { mark(duration); # if defined(LOCAL_DEBUG) Serial.print(F("M=")); # endif } # if defined(LOCAL_DEBUG) Serial.println(duration); # endif } #endif } /** * New function using an 8 byte tick (50 us) timing array in FLASH to save program memory * Raw data starts with a Mark. No leading space as in received timing data! */ void IRsend::sendRaw_P(const uint8_t aBufferWithTicks[], uint_fast16_t aLengthOfBuffer, uint_fast8_t aIRFrequencyKilohertz) { #if !defined(__AVR__) sendRaw(aBufferWithTicks, aLengthOfBuffer, aIRFrequencyKilohertz); // Let the function work for non AVR platforms #else // Set IR carrier frequency enableIROut(aIRFrequencyKilohertz); uint_fast16_t duration; for (uint_fast16_t i = 0; i < aLengthOfBuffer; i++) { duration = pgm_read_byte(&aBufferWithTicks[i]) * (uint_fast16_t) MICROS_PER_TICK; if (i & 1) { // Odd space(duration); # if defined(LOCAL_DEBUG) Serial.print(F("S=")); # endif } else { mark(duration); # if defined(LOCAL_DEBUG) Serial.print(F("M=")); # endif } } IRLedOff(); // Always end with the LED off # if defined(LOCAL_DEBUG) Serial.println(duration); # endif #endif } /** * Sends PulseDistance data from array * For LSB First the LSB of array[0] is sent first then all bits until MSB of array[0]. Next is LSB of array[1] and so on. * The output always ends with a space * Stop bit is always sent * @param aFlags Evaluated flags are PROTOCOL_IS_MSB_FIRST and SUPPRESS_STOP_BIT_FOR_THIS_DATA. Stop bit is otherwise sent for all pulse distance protocols. */ void IRsend::sendPulseDistanceWidthFromArray(uint_fast8_t aFrequencyKHz, DistanceWidthTimingInfoStruct *aDistanceWidthTimingInfo, IRRawDataType *aDecodedRawDataArray, uint16_t aNumberOfBits, uint8_t aFlags, uint16_t aRepeatPeriodMillis, int_fast8_t aNumberOfRepeats) { sendPulseDistanceWidthFromArray(aFrequencyKHz, aDistanceWidthTimingInfo->HeaderMarkMicros, aDistanceWidthTimingInfo->HeaderSpaceMicros, aDistanceWidthTimingInfo->OneMarkMicros, aDistanceWidthTimingInfo->OneSpaceMicros, aDistanceWidthTimingInfo->ZeroMarkMicros, aDistanceWidthTimingInfo->ZeroSpaceMicros, aDecodedRawDataArray, aNumberOfBits, aFlags, aRepeatPeriodMillis, aNumberOfRepeats); } void IRsend::sendPulseDistanceWidthFromArray(uint_fast8_t aFrequencyKHz, uint16_t aHeaderMarkMicros, uint16_t aHeaderSpaceMicros, uint16_t aOneMarkMicros, uint16_t aOneSpaceMicros, uint16_t aZeroMarkMicros, uint16_t aZeroSpaceMicros, IRRawDataType *aDecodedRawDataArray, uint16_t aNumberOfBits, uint8_t aFlags, uint16_t aRepeatPeriodMillis, int_fast8_t aNumberOfRepeats) { // Set IR carrier frequency enableIROut(aFrequencyKHz); uint_fast8_t tNumberOfCommands = aNumberOfRepeats + 1; uint_fast8_t tNumberOf32Or64BitChunks = ((aNumberOfBits - 1) / BITS_IN_RAW_DATA_TYPE) + 1; #if defined(LOCAL_DEBUG) // fist data Serial.print(F("Data[0]=0x")); Serial.print(aDecodedRawDataArray[0], HEX); if (tNumberOf32Or64BitChunks > 1) { Serial.print(F(" Data[1]=0x")); Serial.print(aDecodedRawDataArray[1], HEX); } Serial.print(F(" #=")); Serial.println(aNumberOfBits); Serial.flush(); #endif while (tNumberOfCommands > 0) { unsigned long tStartOfFrameMillis = millis(); // Header mark(aHeaderMarkMicros); space(aHeaderSpaceMicros); for (uint_fast8_t i = 0; i < tNumberOf32Or64BitChunks; ++i) { uint8_t tNumberOfBitsForOneSend; // Manage stop bit uint8_t tFlags; if (i == (tNumberOf32Or64BitChunks - 1)) { // End of data tNumberOfBitsForOneSend = aNumberOfBits; tFlags = aFlags; } else { // intermediate data tNumberOfBitsForOneSend = BITS_IN_RAW_DATA_TYPE; tFlags = aFlags | SUPPRESS_STOP_BIT_FOR_THIS_DATA; // No stop bit for leading data } sendPulseDistanceWidthData(aOneMarkMicros, aOneSpaceMicros, aZeroMarkMicros, aZeroSpaceMicros, aDecodedRawDataArray[i], tNumberOfBitsForOneSend, tFlags); aNumberOfBits -= BITS_IN_RAW_DATA_TYPE; } tNumberOfCommands--; // skip last delay! if (tNumberOfCommands > 0) { /* * Check and fallback for wrong RepeatPeriodMillis parameter. I.e the repeat period must be greater than each frame duration. */ auto tFrameDurationMillis = millis() - tStartOfFrameMillis; if (aRepeatPeriodMillis > tFrameDurationMillis) { delay(aRepeatPeriodMillis - tFrameDurationMillis); } } } } /** * Sends PulseDistance data from array using PulseDistanceWidthProtocolConstants * For LSB First the LSB of array[0] is sent first then all bits until MSB of array[0]. Next is LSB of array[1] and so on. * The output always ends with a space * Stop bit is always sent * @param aNumberOfBits Number of bits from aDecodedRawDataArray to be actually sent. * @param aNumberOfRepeats If < 0 and a aProtocolConstants->SpecialSendRepeatFunction() is specified * then it is called without leading and trailing space. */ void IRsend::sendPulseDistanceWidthFromArray(PulseDistanceWidthProtocolConstants *aProtocolConstants, IRRawDataType *aDecodedRawDataArray, uint16_t aNumberOfBits, int_fast8_t aNumberOfRepeats) { // Calling sendPulseDistanceWidthFromArray() costs 68 bytes program memory compared to the implementation below // sendPulseDistanceWidthFromArray(aProtocolConstants->FrequencyKHz, aProtocolConstants->DistanceWidthTimingInfo.HeaderMarkMicros, // aProtocolConstants->DistanceWidthTimingInfo.HeaderSpaceMicros, // aProtocolConstants->DistanceWidthTimingInfo.OneMarkMicros, aProtocolConstants->DistanceWidthTimingInfo.OneSpaceMicros, // aProtocolConstants->DistanceWidthTimingInfo.ZeroMarkMicros, aProtocolConstants->DistanceWidthTimingInfo.ZeroSpaceMicros, // aDecodedRawDataArray, aNumberOfBits, aProtocolConstants->Flags, aProtocolConstants->RepeatPeriodMillis, // aNumberOfRepeats); // Set IR carrier frequency enableIROut(aProtocolConstants->FrequencyKHz); uint_fast8_t tNumberOf32Or64BitChunks = ((aNumberOfBits - 1) / BITS_IN_RAW_DATA_TYPE) + 1; #if defined(LOCAL_DEBUG) // fist data Serial.print(F("Data[0]=0x")); Serial.print(aDecodedRawDataArray[0], HEX); if (tNumberOf32Or64BitChunks > 1) { Serial.print(F(" Data[1]=0x")); Serial.print(aDecodedRawDataArray[1], HEX); } Serial.print(F(" #=")); Serial.println(aNumberOfBits); Serial.flush(); #endif uint_fast8_t tNumberOfCommands = aNumberOfRepeats + 1; while (tNumberOfCommands > 0) { auto tStartOfFrameMillis = millis(); auto tNumberOfBits = aNumberOfBits; // refresh value for repeats // Header mark(aProtocolConstants->DistanceWidthTimingInfo.HeaderMarkMicros); space(aProtocolConstants->DistanceWidthTimingInfo.HeaderSpaceMicros); uint8_t tOriginalFlags = aProtocolConstants->Flags; for (uint_fast8_t i = 0; i < tNumberOf32Or64BitChunks; ++i) { uint8_t tNumberOfBitsForOneSend; uint8_t tFlags; if (i == (tNumberOf32Or64BitChunks - 1)) { // End of data tNumberOfBitsForOneSend = tNumberOfBits; tFlags = tOriginalFlags; } else { // intermediate data tNumberOfBitsForOneSend = BITS_IN_RAW_DATA_TYPE; tFlags = tOriginalFlags | SUPPRESS_STOP_BIT_FOR_THIS_DATA; // No stop bit for leading data } sendPulseDistanceWidthData(aProtocolConstants->DistanceWidthTimingInfo.OneMarkMicros, aProtocolConstants->DistanceWidthTimingInfo.OneSpaceMicros, aProtocolConstants->DistanceWidthTimingInfo.ZeroMarkMicros, aProtocolConstants->DistanceWidthTimingInfo.ZeroSpaceMicros, aDecodedRawDataArray[i], tNumberOfBitsForOneSend, tFlags); tNumberOfBits -= BITS_IN_RAW_DATA_TYPE; } tNumberOfCommands--; // skip last delay! if (tNumberOfCommands > 0) { /* * Check and fallback for wrong RepeatPeriodMillis parameter. I.e the repeat period must be greater than each frame duration. */ auto tFrameDurationMillis = millis() - tStartOfFrameMillis; if (aProtocolConstants->RepeatPeriodMillis > tFrameDurationMillis) { delay(aProtocolConstants->RepeatPeriodMillis - tFrameDurationMillis); } } } } /** * Sends PulseDistance frames and repeats * @param aProtocolConstants The constants to use for sending this protocol. * @param aData uint32 or uint64 holding the bits to be sent. * @param aNumberOfBits Number of bits from aData to be actually sent. * @param aNumberOfRepeats If < 0 and a aProtocolConstants->SpecialSendRepeatFunction() is specified * then it is called without leading and trailing space. */ void IRsend::sendPulseDistanceWidth(PulseDistanceWidthProtocolConstants *aProtocolConstants, IRRawDataType aData, uint_fast8_t aNumberOfBits, int_fast8_t aNumberOfRepeats) { #if defined(LOCAL_DEBUG) Serial.print(F("Data=0x")); Serial.print(aData, HEX); Serial.print(F(" #=")); Serial.println(aNumberOfBits); Serial.flush(); #endif if (aNumberOfRepeats < 0) { if (aProtocolConstants->SpecialSendRepeatFunction != NULL) { /* * Send only a special repeat and return */ aProtocolConstants->SpecialSendRepeatFunction(); return; } else { // Send only one plain frame (as repeat) aNumberOfRepeats = 0; } } // Set IR carrier frequency enableIROut(aProtocolConstants->FrequencyKHz); uint_fast8_t tNumberOfCommands = aNumberOfRepeats + 1; while (tNumberOfCommands > 0) { unsigned long tStartOfFrameMillis = millis(); if (tNumberOfCommands < ((uint_fast8_t) aNumberOfRepeats + 1) && aProtocolConstants->SpecialSendRepeatFunction != NULL) { // send special repeat, if specified and we are not in the first loop aProtocolConstants->SpecialSendRepeatFunction(); } else { /* * Send Header and regular frame */ mark(aProtocolConstants->DistanceWidthTimingInfo.HeaderMarkMicros); space(aProtocolConstants->DistanceWidthTimingInfo.HeaderSpaceMicros); sendPulseDistanceWidthData(aProtocolConstants, aData, aNumberOfBits); } tNumberOfCommands--; // skip last delay! if (tNumberOfCommands > 0) { auto tCurrentFrameDurationMillis = millis() - tStartOfFrameMillis; /* * Check and fallback for wrong RepeatPeriodMillis parameter. I.e the repeat period must be greater than each frame duration. */ if (aProtocolConstants->RepeatPeriodMillis > tCurrentFrameDurationMillis) { delay(aProtocolConstants->RepeatPeriodMillis - tCurrentFrameDurationMillis); } } } } /** * Sends PulseDistance frames and repeats. * @param aFrequencyKHz, aHeaderMarkMicros, aHeaderSpaceMicros, aOneMarkMicros, aOneSpaceMicros, aZeroMarkMicros, aZeroSpaceMicros, aFlags, aRepeatPeriodMillis Values to use for sending this protocol, also contained in the PulseDistanceWidthProtocolConstants of this protocol. * @param aData uint32 or uint64 holding the bits to be sent. * @param aNumberOfBits Number of bits from aData to be actually sent. * @param aFlags Evaluated flags are PROTOCOL_IS_MSB_FIRST and SUPPRESS_STOP_BIT_FOR_THIS_DATA. Stop bit is otherwise sent for all pulse distance protocols. * @param aNumberOfRepeats If < 0 and a aProtocolConstants->SpecialSendRepeatFunction() is specified * then it is called without leading and trailing space. * @param aSpecialSendRepeatFunction If NULL, the first frame is repeated completely, otherwise this function is used for sending the repeat frame. */ void IRsend::sendPulseDistanceWidth(uint_fast8_t aFrequencyKHz, uint16_t aHeaderMarkMicros, uint16_t aHeaderSpaceMicros, uint16_t aOneMarkMicros, uint16_t aOneSpaceMicros, uint16_t aZeroMarkMicros, uint16_t aZeroSpaceMicros, IRRawDataType aData, uint_fast8_t aNumberOfBits, uint8_t aFlags, uint16_t aRepeatPeriodMillis, int_fast8_t aNumberOfRepeats, void (*aSpecialSendRepeatFunction)()) { if (aNumberOfRepeats < 0) { if (aSpecialSendRepeatFunction != NULL) { aSpecialSendRepeatFunction(); return; } else { aNumberOfRepeats = 0; // send a plain frame as repeat } } // Set IR carrier frequency enableIROut(aFrequencyKHz); uint_fast8_t tNumberOfCommands = aNumberOfRepeats + 1; while (tNumberOfCommands > 0) { unsigned long tStartOfFrameMillis = millis(); if (tNumberOfCommands < ((uint_fast8_t) aNumberOfRepeats + 1) && aSpecialSendRepeatFunction != NULL) { // send special repeat aSpecialSendRepeatFunction(); } else { // Header and regular frame mark(aHeaderMarkMicros); space(aHeaderSpaceMicros); sendPulseDistanceWidthData(aOneMarkMicros, aOneSpaceMicros, aZeroMarkMicros, aZeroSpaceMicros, aData, aNumberOfBits, aFlags); } tNumberOfCommands--; // skip last delay! if (tNumberOfCommands > 0) { /* * Check and fallback for wrong RepeatPeriodMillis parameter. I.e the repeat period must be greater than each frame duration. */ auto tFrameDurationMillis = millis() - tStartOfFrameMillis; if (aRepeatPeriodMillis > tFrameDurationMillis) { delay(aRepeatPeriodMillis - tFrameDurationMillis); } } } } /** * Sends PulseDistance from data contained in parameter using ProtocolConstants structure for timing etc. * The output always ends with a space * Each additional call costs 16 bytes program memory * @param aProtocolConstants The constants to use for sending this protocol. * @param aData uint32 or uint64 holding the bits to be sent. * @param aNumberOfBits Number of bits from aData to be actually sent. */ void IRsend::sendPulseDistanceWidthData(PulseDistanceWidthProtocolConstants *aProtocolConstants, IRRawDataType aData, uint_fast8_t aNumberOfBits) { sendPulseDistanceWidthData(aProtocolConstants->DistanceWidthTimingInfo.OneMarkMicros, aProtocolConstants->DistanceWidthTimingInfo.OneSpaceMicros, aProtocolConstants->DistanceWidthTimingInfo.ZeroMarkMicros, aProtocolConstants->DistanceWidthTimingInfo.ZeroSpaceMicros, aData, aNumberOfBits, aProtocolConstants->Flags); } /** * Sends PulseDistance data with timing parameters and flag parameters. * The output always ends with a space * @param aOneMarkMicros Timing for sending this protocol. * @param aData uint32 or uint64 holding the bits to be sent. * @param aNumberOfBits Number of bits from aData to be actually sent. * @param aFlags Evaluated flags are PROTOCOL_IS_MSB_FIRST and SUPPRESS_STOP_BIT_FOR_THIS_DATA. Stop bit is otherwise sent for all pulse distance protocols. */ void IRsend::sendPulseDistanceWidthData(uint16_t aOneMarkMicros, uint16_t aOneSpaceMicros, uint16_t aZeroMarkMicros, uint16_t aZeroSpaceMicros, IRRawDataType aData, uint_fast8_t aNumberOfBits, uint8_t aFlags) { #if defined(LOCAL_DEBUG) Serial.print(aData, HEX); Serial.print('|'); Serial.println(aNumberOfBits); Serial.flush(); #endif // For MSBFirst, send data from MSB to LSB until mask bit is shifted out IRRawDataType tMask = 1ULL << (aNumberOfBits - 1); for (uint_fast8_t i = aNumberOfBits; i > 0; i--) { if (((aFlags & PROTOCOL_IS_MSB_FIRST) && (aData & tMask)) || (!(aFlags & PROTOCOL_IS_MSB_FIRST) && (aData & 1))) { #if defined(LOCAL_TRACE) Serial.print('1'); #endif mark(aOneMarkMicros); space(aOneSpaceMicros); } else { #if defined(LOCAL_TRACE) Serial.print('0'); #endif mark(aZeroMarkMicros); space(aZeroSpaceMicros); } if (aFlags & PROTOCOL_IS_MSB_FIRST) { tMask >>= 1; } else { aData >>= 1; } } /* * Stop bit is sent for all pulse distance protocols i.e. aOneMarkMicros == aZeroMarkMicros. * Therefore it is not sent for Sony and Magiquest :-) * For sending from an array, no intermediate stop bit must be sent for first data chunk. */ if (!(aFlags & SUPPRESS_STOP_BIT_FOR_THIS_DATA) && aOneMarkMicros == aZeroMarkMicros) { // Send stop bit here #if defined(LOCAL_TRACE) Serial.print('S'); #endif mark(aZeroMarkMicros); // Use aZeroMarkMicros for stop bits. This seems to be correct for all protocols :-) } #if defined(LOCAL_TRACE) Serial.println(); #endif } /** * Sends Biphase data MSB first * Always send start bit, 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 * @param aData uint32 or uint64 holding the bits to be sent. * @param aNumberOfBits Number of bits from aData to be actually sent. */ void IRsend::sendBiphaseData(uint16_t aBiphaseTimeUnit, uint32_t aData, uint_fast8_t aNumberOfBits) { IR_TRACE_PRINT(F("0x")); IR_TRACE_PRINT(aData, HEX); #if defined(LOCAL_TRACE) Serial.print('S'); #endif // Data - Biphase code MSB first // 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--) { bool tCurrentBitIsOne = tNextBitIsOne; tMask >>= 1; tNextBitIsOne = ((aData & tMask) != 0) || (i == 1); // true for last bit to avoid extension of mark if (tCurrentBitIsOne) { #if defined(LOCAL_TRACE) Serial.print('1'); #endif space(aBiphaseTimeUnit); if (tNextBitIsOne) { mark(aBiphaseTimeUnit); } else { // if next bit is 0, extend the current mark in order to generate a continuous signal without short breaks mark(2 * aBiphaseTimeUnit); } tLastBitValue = 1; } else { #if defined(LOCAL_TRACE) Serial.print('0'); #endif if (!tLastBitValue) { mark(aBiphaseTimeUnit); } space(aBiphaseTimeUnit); tLastBitValue = 0; } } IR_TRACE_PRINTLN(F("")); } /** * Sends an IR mark for the specified number of microseconds. * The mark output is modulated at the PWM frequency if USE_NO_SEND_PWM is not defined. * The output is guaranteed to be OFF / inactive after after the call of the function. * This function may affect the state of feedback LED. * Period time is 26 us for 38.46 kHz, 27 us for 37.04 kHz, 25 us for 40 kHz. * On time is 8 us for 30% duty cycle * * The mark() function relies on the correct implementation of: * delayMicroseconds() for pulse time, and micros() for pause time. * The delayMicroseconds() of pulse time is guarded on AVR CPU's with noInterrupts() / interrupts(). * At the start of pause time, interrupts are enabled once, the rest of the pause is also guarded on AVR CPU's with noInterrupts() / interrupts(). * The maximum length of an interrupt during sending should not exceed 26 us - 8 us = 18 us, otherwise timing is disturbed. * This disturbance is no problem, if the exceedance is small and does not happen too often. */ void IRsend::mark(uint16_t aMarkMicros) { #if defined(SEND_PWM_BY_TIMER) || defined(USE_NO_SEND_PWM) # if !defined(NO_LED_FEEDBACK_CODE) if (FeedbackLEDControl.LedFeedbackEnabled == LED_FEEDBACK_ENABLED_FOR_SEND) { setFeedbackLED(true); } # endif #endif #if defined(SEND_PWM_BY_TIMER) /* * Generate hardware PWM signal */ enableSendPWMByTimer(); // Enable timer or ledcWrite() generated PWM output customDelayMicroseconds(aMarkMicros); IRLedOff(); // disables hardware PWM and manages feedback LED return; #elif defined(USE_NO_SEND_PWM) /* * Here we generate no carrier PWM, just simulate an active low receiver signal. */ # if defined(USE_OPEN_DRAIN_OUTPUT_FOR_SEND_PIN) && !defined(OUTPUT_OPEN_DRAIN) pinModeFast(sendPin, OUTPUT); // active state for mimicking open drain # else digitalWriteFast(sendPin, LOW); // Set output to active low. # endif customDelayMicroseconds(aMarkMicros); IRLedOff(); # if !defined(NO_LED_FEEDBACK_CODE) if (FeedbackLEDControl.LedFeedbackEnabled == LED_FEEDBACK_ENABLED_FOR_SEND) { setFeedbackLED(false); } return; # endif #else // defined(SEND_PWM_BY_TIMER) /* * Generate PWM by bit banging */ unsigned long tStartMicros = micros(); unsigned long tNextPeriodEnding = tStartMicros; unsigned long tMicros; # if !defined(NO_LED_FEEDBACK_CODE) bool FeedbackLedIsActive = false; # endif do { // digitalToggleFast(_IR_TIMING_TEST_PIN); /* * Output the PWM pulse */ noInterrupts(); // do not let interrupts extend the short on period # if defined(USE_OPEN_DRAIN_OUTPUT_FOR_SEND_PIN) # if defined(OUTPUT_OPEN_DRAIN) digitalWriteFast(sendPin, LOW); // set output with pin mode OUTPUT_OPEN_DRAIN to active low # else pinModeFast(sendPin, OUTPUT); // active state for mimicking open drain # endif # else // 3.5 us from FeedbackLed on to pin setting. 5.7 us from call of mark() to pin setting incl. setting of feedback pin. // 4.3 us from do{ to pin setting if sendPin is no constant digitalWriteFast(sendPin, HIGH); # endif delayMicroseconds (periodOnTimeMicros); // On time is 8 us for 30% duty cycle. This is normally implemented by a blocking wait. /* * Output the PWM pause */ # if defined(USE_OPEN_DRAIN_OUTPUT_FOR_SEND_PIN) && !defined(OUTPUT_OPEN_DRAIN) # if defined(OUTPUT_OPEN_DRAIN) digitalWriteFast(sendPin, HIGH); // Set output with pin mode OUTPUT_OPEN_DRAIN to inactive high. # else pinModeFast(sendPin, INPUT); // to mimic the open drain inactive state # endif # else digitalWriteFast(sendPin, LOW); # endif /* * Enable interrupts at start of the longer off period. Required at least to keep micros correct. * If receive interrupt is still active, it takes 3.4 us from now until receive ISR is active (for 7 us + pop's) */ interrupts(); # if !defined(NO_LED_FEEDBACK_CODE) /* * Delayed call of setFeedbackLED() to get better startup timing, especially required for consecutive marks */ if (!FeedbackLedIsActive) { FeedbackLedIsActive = true; if (FeedbackLEDControl.LedFeedbackEnabled == LED_FEEDBACK_ENABLED_FOR_SEND) { setFeedbackLED(true); } } # endif /* * PWM pause timing * Measured delta between pause duration values are 13 us for a 16 MHz Uno (from 13 to 26), if interrupts are disabled below * Measured delta between pause duration values are 20 us for a 16 MHz Uno (from 7.8 to 28), if interrupts are not disabled below * Minimal pause duration is 5.2 us with NO_LED_FEEDBACK_CODE enabled * and 8.1 us with NO_LED_FEEDBACK_CODE disabled. */ tNextPeriodEnding += periodTimeMicros; #if defined(__AVR__) // micros() for STM sometimes give decreasing values if interrupts are disabled. See https://github.com/stm32duino/Arduino_Core_STM32/issues/1680 noInterrupts(); // disable interrupts (especially the 20 us receive interrupts) only at start of the PWM pause. Otherwise it may extend the pause too much. #endif do { #if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN) digitalWriteFast(_IR_TIMING_TEST_PIN, HIGH); // 2 clock cycles #endif /* * For AVR @16MHz we have only 4 us resolution. * The duration of the micros() call itself is 3 us. * It takes 0.9 us from signal going low here. * The rest of the loop takes 1.2 us with NO_LED_FEEDBACK_CODE enabled * and 3 us with NO_LED_FEEDBACK_CODE disabled. */ #if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN) digitalWriteFast(_IR_TIMING_TEST_PIN, LOW); // 2 clock cycles #endif /* * Exit the forever loop if aMarkMicros has reached */ tMicros = micros(); uint16_t tDeltaMicros = tMicros - tStartMicros; #if defined(__AVR__) // reset feedback led in the last pause before end // tDeltaMicros += (160 / CLOCKS_PER_MICRO); // adding this once increases program size, so do it below ! # if !defined(NO_LED_FEEDBACK_CODE) if (tDeltaMicros >= aMarkMicros - (30 + (112 / CLOCKS_PER_MICRO))) { // 30 to be constant. Using periodTimeMicros increases program size too much. if (FeedbackLEDControl.LedFeedbackEnabled == LED_FEEDBACK_ENABLED_FOR_SEND) { setFeedbackLED(false); } } # endif // Just getting variables and check for end condition takes minimal 3.8 us if (tDeltaMicros >= aMarkMicros - (112 / CLOCKS_PER_MICRO)) { // To compensate for call duration - 112 is an empirical value #else if (tDeltaMicros >= aMarkMicros) { # if !defined(NO_LED_FEEDBACK_CODE) if (FeedbackLEDControl.LedFeedbackEnabled == LED_FEEDBACK_ENABLED_FOR_SEND) { setFeedbackLED(false); } # endif #endif #if defined(__AVR__) interrupts(); #endif return; } } while (tMicros < tNextPeriodEnding); } while (true); # endif } /** * Just switch the IR sending LED off to send an IR space * A space is "no output", so the PWM output is disabled. * This function may affect the state of feedback LED. */ void IRsend::IRLedOff() { #if defined(SEND_PWM_BY_TIMER) disableSendPWMByTimer(); // Disable PWM output #elif defined(USE_NO_SEND_PWM) # if defined(USE_OPEN_DRAIN_OUTPUT_FOR_SEND_PIN) && !defined(OUTPUT_OPEN_DRAIN) digitalWriteFast(sendPin, LOW); // prepare for all next active states. pinModeFast(sendPin, INPUT);// inactive state for open drain # else digitalWriteFast(sendPin, HIGH); // Set output to inactive high. # endif #else # if defined(USE_OPEN_DRAIN_OUTPUT_FOR_SEND_PIN) # if defined(OUTPUT_OPEN_DRAIN) digitalWriteFast(sendPin, HIGH); // Set output to inactive high. # else pinModeFast(sendPin, INPUT); // inactive state to mimic open drain # endif # else digitalWriteFast(sendPin, LOW); # endif #endif #if !defined(NO_LED_FEEDBACK_CODE) if (FeedbackLEDControl.LedFeedbackEnabled == LED_FEEDBACK_ENABLED_FOR_SEND) { setFeedbackLED(false); } #endif } /** * Sends an IR space for the specified number of microseconds. * A space is "no output", so just wait. */ void IRsend::space(uint16_t aSpaceMicros) { customDelayMicroseconds(aSpaceMicros); } /** * Custom delay function that circumvents Arduino's delayMicroseconds 16 bit limit * and is (mostly) not extended by the duration of interrupt codes like the millis() interrupt */ void IRsend::customDelayMicroseconds(unsigned long aMicroseconds) { #if defined(ESP32) || defined(ESP8266) // from https://github.com/crankyoldgit/IRremoteESP8266/blob/00b27cc7ea2e7ac1e48e91740723c805a38728e0/src/IRsend.cpp#L123 // Invoke a delay(), where possible, to avoid triggering the WDT. // see https://github.com/Arduino-IRremote/Arduino-IRremote/issues/1114 for the reason of checking for > 16383) // delayMicroseconds() is only accurate to 16383 us. Ref: https://www.arduino.cc/en/Reference/delayMicroseconds if (aMicroseconds > 16383) { delay(aMicroseconds / 1000UL); // Delay for as many whole milliseconds as we can. // Delay the remaining sub-millisecond. delayMicroseconds(static_cast(aMicroseconds % 1000UL)); } else { delayMicroseconds(aMicroseconds); } #else # if defined(__AVR__) unsigned long start = micros() - (64 / clockCyclesPerMicrosecond()); // - (64 / clockCyclesPerMicrosecond()) for reduced resolution and additional overhead # else unsigned long start = micros(); # endif // overflow invariant comparison :-) while (micros() - start < aMicroseconds) { } #endif } /** * Enables IR output. The kHz value controls the modulation frequency in kilohertz. * IF PWM should be generated by a timer, it uses the platform specific timerConfigForSend() function, * otherwise it computes the delays used by the mark() function. * If IR_SEND_PIN is defined, maximum PWM frequency for an AVR @16 MHz is 170 kHz (180 kHz if NO_LED_FEEDBACK_CODE is defined) */ 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 #elif defined(USE_NO_SEND_PWM) (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 # 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 # endif #endif // defined(SEND_PWM_BY_TIMER) #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); # else pinModeFast(sendPin, OUTPUT_OPEN_DRAIN); # endif #else // 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__) || !defined(SEND_PWM_BY_TIMER) # if defined(IR_SEND_PIN) pinModeFast(IR_SEND_PIN, OUTPUT); # else pinModeFast(sendPin, OUTPUT); # endif # endif #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; } /** @}*/ #if defined(_IR_MEASURE_TIMING) #undef _IR_MEASURE_TIMING #endif #if defined(LOCAL_TRACE) #undef LOCAL_TRACE #endif #if defined(LOCAL_DEBUG) #undef LOCAL_DEBUG #endif #endif // _IR_SEND_HPP