/* * TinyIRReceiver.hpp * * Receives IR data of NEC protocol using pin change interrupts. * NEC is the protocol of most cheap remote controls for Arduino. * * Parity check is done for address and data. * On a completely received IR command, the user function handleReceivedIRData(uint8_t aAddress, uint8_t aCommand, uint8_t aFlags) * is called in interrupt context but with interrupts being enabled to enable use of delay() etc. * !!!!!!!!!!!!!!!!!!!!!! * Functions called in interrupt context should be running as short as possible, * so if you require longer action, save the data (address + command) and handle them in the main loop. * !!!!!!!!!!!!!!!!!!!!! * aFlags can contain one of IRDATA_FLAGS_EMPTY, IRDATA_FLAGS_IS_REPEAT and IRDATA_FLAGS_PARITY_FAILED bits * * The FAST protocol is a proprietary modified JVC protocol without address, with parity and with a shorter header. * FAST Protocol characteristics: * - Bit timing is like NEC or JVC * - The header is shorter, 3156 vs. 12500 * - No address and 16 bit data, interpreted as 8 bit command and 8 bit inverted command, * leading to a fixed protocol length of (6 + (16 * 3) + 1) * 526 = 55 * 526 = 28930 microseconds or 29 ms. * - Repeats are sent as complete frames but in a 50 ms period / with a 21 ms distance. * * * This file is part of IRMP https://github.com/IRMP-org/IRMP. * This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote. * ************************************************************************************ * MIT License * * Copyright (c) 2022-2025 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. * ************************************************************************************ */ /* * This library can be configured at compile time by the following options / macros: * For more details see: https://github.com/Arduino-IRremote/Arduino-IRremote#compile-options--macros-for-this-library (scroll down) * * - IR_RECEIVE_PIN The pin number for TinyIRReceiver IR input. * - IR_FEEDBACK_LED_PIN The pin number for TinyIRReceiver feedback LED. * - NO_LED_FEEDBACK_CODE Disables the feedback LED function for send and receive. Saves 14 bytes program memory. * - NO_LED_RECEIVE_FEEDBACK_CODE Disables the LED feedback code for receive. * - NO_LED_SEND_FEEDBACK_CODE Disables the LED feedback code for send. * - DISABLE_PARITY_CHECKS Disable parity checks. Saves 48 bytes of program memory. * - USE_EXTENDED_NEC_PROTOCOL Like NEC, but take the 16 bit address as one 16 bit value and not as 8 bit normal and 8 bit inverted value. * - USE_ONKYO_PROTOCOL Like NEC, but take the 16 bit address and command each as one 16 bit value and not as 8 bit normal and 8 bit inverted value. * - USE_FAST_PROTOCOL Use FAST protocol (no address and 16 bit data, interpreted as 8 bit command and 8 bit inverted command) instead of NEC. * - ENABLE_NEC2_REPEATS Instead of sending / receiving the NEC special repeat code, send / receive the original frame for repeat. * - USE_CALLBACK_FOR_TINY_RECEIVER Call the user provided function "void handleReceivedTinyIRData()" each time a frame or repeat is received. */ #ifndef _TINY_IR_RECEIVER_HPP #define _TINY_IR_RECEIVER_HPP #include /* * Protocol selection */ //#define DISABLE_PARITY_CHECKS // Disable parity checks. Saves 48 bytes of program memory. //#define USE_EXTENDED_NEC_PROTOCOL // Like NEC, but take the 16 bit address as one 16 bit value and not as 8 bit normal and 8 bit inverted value. //#define USE_ONKYO_PROTOCOL // Like NEC, but take the 16 bit address and command each as one 16 bit value and not as 8 bit normal and 8 bit inverted value. //#define USE_FAST_PROTOCOL // Use FAST protocol instead of NEC / ONKYO. //#define ENABLE_NEC2_REPEATS // Instead of sending / receiving the NEC special repeat code, send / receive the original frame for repeat. //#define IR_RECEIVE_PIN 2 //#define NO_LED_RECEIVE_FEEDBACK_CODE // Disables the LED feedback code for receive. //#define IR_FEEDBACK_LED_PIN 12 // Use this, to disable use of LED_BUILTIN definition for IR_FEEDBACK_LED_PIN #include "TinyIR.h" #include "digitalWriteFast.h" /** \addtogroup TinyReceiver Minimal receiver for NEC and FAST protocol * @{ */ #if defined(DEBUG) #define LOCAL_DEBUG #define LOCAL_DEBUG_ATTACH_INTERRUPT #else //#define LOCAL_DEBUG // This enables debug output only for this file //#define LOCAL_DEBUG_ATTACH_INTERRUPT // To see if attachInterrupt() or static interrupt (by register tweaking) is used and no other debug output #endif #if defined(TRACE) #define LOCAL_TRACE_STATE_MACHINE #else //#define LOCAL_TRACE_STATE_MACHINE // to see the state of the ISR (Interrupt Service Routine) state machine #endif //#define _IR_MEASURE_TIMING // Activate this if you want to enable internal hardware timing measurement. //#define _IR_TIMING_TEST_PIN 7 TinyIRReceiverStruct TinyIRReceiverControl; volatile TinyIRReceiverCallbackDataStruct TinyIRReceiverData; /* * Set input pin and output pin definitions etc. */ #if defined(IR_INPUT_PIN) #warning "IR_INPUT_PIN is deprecated, use IR_RECEIVE_PIN" #define IR_RECEIVE_PIN IR_INPUT_PIN #endif #if !defined(IR_RECEIVE_PIN) # if defined(__AVR_ATtiny1616__) || defined(__AVR_ATtiny3216__) || defined(__AVR_ATtiny3217__) #warning "IR_RECEIVE_PIN is not defined, so it is set to 10" #define IR_RECEIVE_PIN 10 # elif defined(__AVR_ATtiny816__) #warning "IR_RECEIVE_PIN is not defined, so it is set to 14" #define IR_RECEIVE_PIN 14 # else #warning "IR_RECEIVE_PIN is not defined, so it is set to 2" #define IR_RECEIVE_PIN 2 # endif #endif #if !( \ (defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)) /* ATtinyX5 */ \ || defined(__AVR_ATtiny88__) /* MH-ET LIVE Tiny88 */ \ || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega1281__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega2561__) \ || defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__) \ || defined(__AVR_ATmega8__) || defined(__AVR_ATmega48__) || defined(__AVR_ATmega48P__) || defined(__AVR_ATmega48PB__) || defined(__AVR_ATmega88P__) || defined(__AVR_ATmega88PB__) \ || defined(__AVR_ATmega168__) || defined(__AVR_ATmega168PA__) || defined(__AVR_ATmega168PB__) || defined(__AVR_ATmega328__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328PB__) \ /* ATmegas with ports 0,1,2 above and ATtiny167 only 2 pins below */ \ || ( (defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__)) && ( (defined(ARDUINO_AVR_DIGISPARKPRO) && ((IR_RECEIVE_PIN == 3) || (IR_RECEIVE_PIN == 9))) /*ATtinyX7(digisparkpro) and pin 3 or 9 */\ || (! defined(ARDUINO_AVR_DIGISPARKPRO) && ((IR_RECEIVE_PIN == 3) || (IR_RECEIVE_PIN == 14)))) ) /*ATtinyX7(ATTinyCore) and pin 3 or 14 */ \ ) #define TINY_RECEIVER_USE_ARDUINO_ATTACH_INTERRUPT // Cannot use any static ISR vector here. In other cases we have code provided for generating interrupt on pin change. #endif /** * Declaration of the callback function provided by the user application. * It is called every time a complete IR command or repeat was received. */ extern void handleReceivedTinyIRData(); uint32_t sMicrosOfGap; // The length of the gap before the start bit, used for trace /** * The ISR (Interrupt Service Routine) of TinyIRRreceiver. * It handles the NEC protocol decoding and calls the user callback function on complete. * 5 us + 3 us for push + pop for a 16MHz ATmega */ #if defined(ESP8266) || defined(ESP32) IRAM_ATTR #endif void IRPinChangeInterruptHandler(void) { #if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN) digitalWriteFast(_IR_TIMING_TEST_PIN, HIGH); // 2 clock cycles #endif /* * Save IR input level * Negative logic, true / HIGH means inactive / IR space, LOW / false means IR mark. */ uint_fast8_t tIRLevel = digitalReadFast(IR_RECEIVE_PIN); #if !defined(NO_LED_RECEIVE_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN) # if defined(FEEDBACK_LED_IS_ACTIVE_LOW) digitalWriteFast(IR_FEEDBACK_LED_PIN, tIRLevel); # else digitalWriteFast(IR_FEEDBACK_LED_PIN, !tIRLevel); # endif #endif /* * 1. compute microseconds after last change */ // Repeats can be sent after a pause, which is longer than 64000 microseconds, so we need a 32 bit value for check of repeats uint32_t tCurrentMicros = micros(); uint32_t tMicrosOfMarkOrSpace32 = tCurrentMicros - TinyIRReceiverControl.LastChangeMicros; uint16_t tMicrosOfMarkOrSpace = tMicrosOfMarkOrSpace32; TinyIRReceiverControl.LastChangeMicros = tCurrentMicros; uint8_t tState = TinyIRReceiverControl.IRReceiverState; #if defined(LOCAL_TRACE_STATE_MACHINE) Serial.print(tState); Serial.print(F(" D=")); Serial.print(tMicrosOfMarkOrSpace); // Serial.print(F(" I=")); // Serial.print(tIRLevel); Serial.print('|'); #endif if (tIRLevel == LOW) { /* * We have a mark here */ if (tMicrosOfMarkOrSpace > 2 * TINY_RECEIVER_HEADER_MARK) { // timeout -> must reset state machine tState = IR_RECEIVER_STATE_WAITING_FOR_START_MARK; } if (tState == IR_RECEIVER_STATE_WAITING_FOR_START_MARK) { // We are at the beginning of the header mark, check timing at the next transition tState = IR_RECEIVER_STATE_WAITING_FOR_START_SPACE; TinyIRReceiverControl.Flags = IRDATA_FLAGS_EMPTY; // If we do it here, it saves 4 bytes #if defined(TRACE) // Do not use LOCAL_TRACE here since sMicrosOfGap is read in a cpp file at TRACE sMicrosOfGap = tMicrosOfMarkOrSpace32; #endif #if defined(ENABLE_NEC2_REPEATS) // Check for repeat, where full frame is sent again after TINY_RECEIVER_REPEAT_PERIOD ms // Not required for NEC, where repeats are detected by a special header space duration // Must use 32 bit arithmetic here! if (tMicrosOfMarkOrSpace32 < TINY_RECEIVER_MAXIMUM_REPEAT_DISTANCE) { TinyIRReceiverControl.Flags = IRDATA_FLAGS_IS_REPEAT; } #endif } else if (tState == IR_RECEIVER_STATE_WAITING_FOR_FIRST_DATA_MARK) { if (tMicrosOfMarkOrSpace >= lowerValue25Percent(TINY_RECEIVER_HEADER_SPACE) && tMicrosOfMarkOrSpace <= upperValue25Percent(TINY_RECEIVER_HEADER_SPACE)) { /* * We had a valid data header space here -> initialize data */ TinyIRReceiverControl.IRRawDataBitCounter = 0; #if (TINY_RECEIVER_BITS > 16) TinyIRReceiverControl.IRRawData.ULong = 0; #else TinyIRReceiverControl.IRRawData.UWord = 0; #endif TinyIRReceiverControl.IRRawDataMask = 1; tState = IR_RECEIVER_STATE_WAITING_FOR_DATA_SPACE; #if !defined(ENABLE_NEC2_REPEATS) // Check for NEC repeat header } else if (tMicrosOfMarkOrSpace >= lowerValue25Percent(NEC_REPEAT_HEADER_SPACE) && tMicrosOfMarkOrSpace <= upperValue25Percent(NEC_REPEAT_HEADER_SPACE) && TinyIRReceiverControl.IRRawDataBitCounter >= TINY_RECEIVER_BITS) { /* * We have a repeat header here and no broken receive before -> set repeat flag */ TinyIRReceiverControl.Flags = IRDATA_FLAGS_IS_REPEAT; tState = IR_RECEIVER_STATE_WAITING_FOR_DATA_SPACE; #endif } else { // This parts are optimized by the compiler into jumps to one code :-) // Wrong length -> reset state tState = IR_RECEIVER_STATE_WAITING_FOR_START_MARK; } } else if (tState == IR_RECEIVER_STATE_WAITING_FOR_DATA_MARK) { // Check data space length if (tMicrosOfMarkOrSpace >= lowerValue50Percent(TINY_RECEIVER_ZERO_SPACE) && tMicrosOfMarkOrSpace <= upperValue50Percent(TINY_RECEIVER_ONE_SPACE)) { // We have a valid bit here tState = IR_RECEIVER_STATE_WAITING_FOR_DATA_SPACE; if (tMicrosOfMarkOrSpace >= 2 * TINY_RECEIVER_UNIT) { // we received a 1 #if (TINY_RECEIVER_BITS > 16) TinyIRReceiverControl.IRRawData.ULong |= TinyIRReceiverControl.IRRawDataMask; #else TinyIRReceiverControl.IRRawData.UWord |= TinyIRReceiverControl.IRRawDataMask; #endif } else { // we received a 0 - empty code for documentation } // prepare for next bit TinyIRReceiverControl.IRRawDataMask = TinyIRReceiverControl.IRRawDataMask << 1; TinyIRReceiverControl.IRRawDataBitCounter++; } else { // Wrong length -> reset state tState = IR_RECEIVER_STATE_WAITING_FOR_START_MARK; } } else { // error wrong state for the received level, e.g. if we missed one change interrupt -> reset state tState = IR_RECEIVER_STATE_WAITING_FOR_START_MARK; } } else { /* * We have a space here */ if (tState == IR_RECEIVER_STATE_WAITING_FOR_START_SPACE) { /* * Check length of header mark here */ if (tMicrosOfMarkOrSpace >= lowerValue25Percent(TINY_RECEIVER_HEADER_MARK) && tMicrosOfMarkOrSpace <= upperValue25Percent(TINY_RECEIVER_HEADER_MARK)) { tState = IR_RECEIVER_STATE_WAITING_FOR_FIRST_DATA_MARK; } else { // Wrong length of header mark -> reset state tState = IR_RECEIVER_STATE_WAITING_FOR_START_MARK; } } else if (tState == IR_RECEIVER_STATE_WAITING_FOR_DATA_SPACE) { // Check data mark length if (tMicrosOfMarkOrSpace >= lowerValue50Percent(TINY_RECEIVER_BIT_MARK) && tMicrosOfMarkOrSpace <= upperValue50Percent(TINY_RECEIVER_BIT_MARK)) { /* * We have a valid mark here, check for transmission complete, i.e. the mark of the stop bit */ if (TinyIRReceiverControl.IRRawDataBitCounter >= TINY_RECEIVER_BITS #if !defined(ENABLE_NEC2_REPEATS) || (TinyIRReceiverControl.Flags & IRDATA_FLAGS_IS_REPEAT) // Do not check for full length received, if we have a short repeat frame #endif ) { /* * Code complete -> optionally check parity */ // Reset state for new start tState = IR_RECEIVER_STATE_WAITING_FOR_START_MARK; #if !defined(DISABLE_PARITY_CHECKS) && (TINY_RECEIVER_ADDRESS_BITS == 16) && TINY_RECEIVER_ADDRESS_HAS_8_BIT_PARITY /* * Check address parity * Address is sent first and contained in the lower word */ if (TinyIRReceiverControl.IRRawData.UBytes[0] != (uint8_t)(~TinyIRReceiverControl.IRRawData.UBytes[1])) { #if defined(ENABLE_NEC2_REPEATS) TinyIRReceiverControl.Flags |= IRDATA_FLAGS_PARITY_FAILED; // here we can have the repeat flag already set #else TinyIRReceiverControl.Flags = IRDATA_FLAGS_PARITY_FAILED; // here we do not check anything, if we have a repeat #endif } #endif #if !defined(DISABLE_PARITY_CHECKS) && (TINY_RECEIVER_COMMAND_BITS == 16) && TINY_RECEIVER_COMMAND_HAS_8_BIT_PARITY /* * Check command parity */ #if (TINY_RECEIVER_ADDRESS_BITS > 0) if (TinyIRReceiverControl.IRRawData.UBytes[2] != (uint8_t)(~TinyIRReceiverControl.IRRawData.UBytes[3])) { #if defined(ENABLE_NEC2_REPEATS) TinyIRReceiverControl.Flags |= IRDATA_FLAGS_PARITY_FAILED; #else TinyIRReceiverControl.Flags = IRDATA_FLAGS_PARITY_FAILED; #endif # if defined(LOCAL_DEBUG) Serial.print(F("Parity check for command failed. Command=")); Serial.print(TinyIRReceiverControl.IRRawData.UBytes[2], HEX); Serial.print(F(" parity=")); Serial.println(TinyIRReceiverControl.IRRawData.UBytes[3], HEX); # endif #else // No address, so command and parity are in the lowest bytes if (TinyIRReceiverControl.IRRawData.UBytes[0] != (uint8_t) (~TinyIRReceiverControl.IRRawData.UBytes[1])) { TinyIRReceiverControl.Flags |= IRDATA_FLAGS_PARITY_FAILED; # if defined(LOCAL_DEBUG) Serial.print(F("Parity check for command failed. Command=")); Serial.print(TinyIRReceiverControl.IRRawData.UBytes[0], HEX); Serial.print(F(" parity=")); Serial.println(TinyIRReceiverControl.IRRawData.UBytes[1], HEX); # endif #endif } #endif /* * Call user provided callback here * The parameter size is dependent of the code variant used in order to save program memory. * We have 6 cases: 0, 8 bit or 16 bit address, each with 8 or 16 bit command */ #if !defined(ARDUINO_ARCH_MBED) && !defined(ESP32) // no Serial etc. in callback for ESP -> no interrupt required, WDT is running! interrupts(); // enable interrupts, so delay() etc. works in callback #endif TinyIRReceiverData.justWritten = true; TinyIRReceiverData.Flags = TinyIRReceiverControl.Flags; #if (TINY_RECEIVER_ADDRESS_BITS > 0) # if TINY_RECEIVER_ADDRESS_HAS_8_BIT_PARITY // Here we have 8 bit address TinyIRReceiverData.Address = TinyIRReceiverControl.IRRawData.UBytes[0]; # else // Here we have 16 bit address TinyIRReceiverData.Address = TinyIRReceiverControl.IRRawData.UWord.LowWord; # endif # if TINY_RECEIVER_COMMAND_HAS_8_BIT_PARITY // Here we have 8 bit command TinyIRReceiverData.Command = TinyIRReceiverControl.IRRawData.UBytes[2]; # else // Here we have 16 bit command TinyIRReceiverData.Command = TinyIRReceiverControl.IRRawData.UWord.HighWord; # endif #else // Here we have NO address # if TINY_RECEIVER_COMMAND_HAS_8_BIT_PARITY // Here we have 8 bit command TinyIRReceiverData.Command = TinyIRReceiverControl.IRRawData.UBytes[0]; # else // Here we have 16 bit command TinyIRReceiverData.Command = TinyIRReceiverControl.IRRawData.UWord; # endif #endif #if defined(USE_CALLBACK_FOR_TINY_RECEIVER) handleReceivedTinyIRData(); #endif } else { // not finished yet tState = IR_RECEIVER_STATE_WAITING_FOR_DATA_MARK; } } else { // Wrong length -> reset state tState = IR_RECEIVER_STATE_WAITING_FOR_START_MARK; } } else { // error wrong state for the received level, e.g. if we missed one change interrupt -> reset state tState = IR_RECEIVER_STATE_WAITING_FOR_START_MARK; } } TinyIRReceiverControl.IRReceiverState = tState; #ifdef _IR_MEASURE_TIMING digitalWriteFast(_IR_TIMING_TEST_PIN, LOW); // 2 clock cycles #endif } bool isTinyReceiverIdle() { return (TinyIRReceiverControl.IRReceiverState == IR_RECEIVER_STATE_WAITING_FOR_START_MARK); } /* * Function to be used as drop in for IrReceiver.decode() */ bool TinyReceiverDecode() { bool tJustWritten = TinyIRReceiverData.justWritten; if (tJustWritten) { TinyIRReceiverData.justWritten = false; } return tJustWritten; } /* * Checks if IR_RECEIVE_PIN is connected and high * @return true, if IR Receiver is attached */ bool isIRReceiverAttachedForTinyReceiver() { pinModeFast(IR_RECEIVE_PIN, OUTPUT); digitalWriteFast(IR_RECEIVE_PIN, LOW); // discharge pin capacity pinModeFast(IR_RECEIVE_PIN, INPUT); return digitalRead(IR_RECEIVE_PIN); // use slow digitalRead here, since the pin capacity is not fully charged again if we use digitalReadFast. } /** * Sets IR_RECEIVE_PIN mode to INPUT, and if IR_FEEDBACK_LED_PIN is defined, sets feedback LED output mode. * Then call enablePCIInterruptForTinyReceiver() */ bool initPCIInterruptForTinyReceiver() { pinModeFast(IR_RECEIVE_PIN, INPUT); #if !defined(NO_LED_RECEIVE_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN) pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT); # if defined(FEEDBACK_LED_IS_ACTIVE_LOW) digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH); # endif #endif return enablePCIInterruptForTinyReceiver(); } void printTinyReceiverResultMinimal(Print *aSerial) { // Print only very short output, since we are in an interrupt context and do not want to miss the next interrupts of the repeats coming soon #if defined(USE_FAST_PROTOCOL) aSerial->print(F("C=0x")); #else aSerial->print(F("A=0x")); aSerial->print(TinyIRReceiverData.Address, HEX); aSerial->print(F(" C=0x")); #endif aSerial->print(TinyIRReceiverData.Command, HEX); if (TinyIRReceiverData.Flags == IRDATA_FLAGS_IS_REPEAT) { aSerial->print(F(" R")); } #if !defined(DISABLE_PARITY_CHECKS) if (TinyIRReceiverData.Flags == IRDATA_FLAGS_PARITY_FAILED) { aSerial->print(F(" P")); } #endif aSerial->println(); } #if defined (LOCAL_DEBUG_ATTACH_INTERRUPT) && !defined(STR) // Helper macro for getting a macro definition as string #define STR_HELPER(x) #x #define STR(x) STR_HELPER(x) #endif /************************************************** * Pin to interrupt mapping for different platforms **************************************************/ #if defined(__AVR_ATtiny816__) || defined(__AVR_ATtiny1616__) || defined(__AVR_ATtiny3216__) || defined(__AVR_ATtiny3217__) #define USE_ATTACH_INTERRUPT_DIRECT #elif !defined(__AVR__) || defined(TINY_RECEIVER_USE_ARDUINO_ATTACH_INTERRUPT) // Default for all NON AVR platforms #define USE_ATTACH_INTERRUPT #else # if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) #define USE_PCIE # elif defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) # if defined(ARDUINO_AVR_DIGISPARKPRO) # if (IR_RECEIVE_PIN == 3) #define USE_INT0 # elif (IR_RECEIVE_PIN == 9) #define USE_INT1 # else # error "IR_RECEIVE_PIN must be 9 or 3." # endif // if (IR_RECEIVE_PIN == 9) # else // defined(ARDUINO_AVR_DIGISPARKPRO) # if (IR_RECEIVE_PIN == 14) #define USE_INT0 # elif (IR_RECEIVE_PIN == 3) #define USE_INT1 # else # error "IR_RECEIVE_PIN must be 14 or 3." # endif // if (IR_RECEIVE_PIN == 14) # endif # elif (defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)) # if (IR_RECEIVE_PIN == 21) #define USE_INT0 # elif (IR_RECEIVE_PIN == 20) #define USE_INT1 # else #warning "No pin mapping for IR_RECEIVE_PIN to interrupt found -> attachInterrupt() is used now." #define USE_ATTACH_INTERRUPT # endif # else // defined(__AVR_ATtiny25__) /* * ATmegas + ATtiny88 here */ # if (IR_RECEIVE_PIN == 2) #define USE_INT0 # elif (IR_RECEIVE_PIN == 3) #define USE_INT1 # elif IR_RECEIVE_PIN == 4 || IR_RECEIVE_PIN == 5 || IR_RECEIVE_PIN == 6 || IR_RECEIVE_PIN == 7 //ATmega328 (Uno, Nano ) etc. Enable pin change interrupt 20 to 23 for port PD4 to PD7 (Arduino pin 4 to 7) #define USE_PCINT2 # elif IR_RECEIVE_PIN == 8 || IR_RECEIVE_PIN == 9 || IR_RECEIVE_PIN == 10 || IR_RECEIVE_PIN == 11 || IR_RECEIVE_PIN == 12 || IR_RECEIVE_PIN == 13 //ATmega328 (Uno, Nano ) etc. Enable pin change interrupt 0 to 5 for port PB0 to PB5 (Arduino pin 8 to 13) #define USE_PCINT0 # elif IR_RECEIVE_PIN == A0 || IR_RECEIVE_PIN == A1 || IR_RECEIVE_PIN == A2 || IR_RECEIVE_PIN == A3 || IR_RECEIVE_PIN == A4 || IR_RECEIVE_PIN == A5 //ATmega328 (Uno, Nano ) etc. Enable pin change interrupt 8 to 13 for port PC0 to PC5 (Arduino pin A0 to A5) #define USE_PCINT1 # else #warning "No pin mapping for IR_RECEIVE_PIN to interrupt found -> attachInterrupt() is used now." #define USE_ATTACH_INTERRUPT # endif // if (IR_RECEIVE_PIN == 2) # endif // defined(__AVR_ATtiny25__) #endif // ! defined(__AVR__) || defined(TINY_RECEIVER_USE_ARDUINO_ATTACH_INTERRUPT) /** * Initializes hardware interrupt generation according to IR_RECEIVE_PIN or use attachInterrupt() function. * @return true if interrupt was successfully enabled */ bool enablePCIInterruptForTinyReceiver() { #if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN) pinModeFast(_IR_TIMING_TEST_PIN, OUTPUT); #endif #if defined(USE_ATTACH_INTERRUPT) || defined(USE_ATTACH_INTERRUPT_DIRECT) # if defined(USE_ATTACH_INTERRUPT) # if defined(NOT_AN_INTERRUPT) // check if IDE has defined the check of digitalPinToInterrupt if(digitalPinToInterrupt(IR_RECEIVE_PIN) == NOT_AN_INTERRUPT){ return false; } # endif // costs 112 bytes program memory + 4 bytes RAM # if defined(ARDUINO_ARCH_SAMD) // see https://www.arduino.cc/reference/tr/language/functions/external-interrupts/attachinterrupt/ paragraph: Syntax attachInterrupt(IR_RECEIVE_PIN, IRPinChangeInterruptHandler, CHANGE); // no extra pin mapping here :-( # else attachInterrupt(digitalPinToInterrupt(IR_RECEIVE_PIN), IRPinChangeInterruptHandler, CHANGE); # endif # else // USE_ATTACH_INTERRUPT_DIRECT here, only defined for ATtinies *16, see above // 2.2 us more than version configured with macros and not compatible attachInterrupt(IR_RECEIVE_PIN, IRPinChangeInterruptHandler, CHANGE); // no extra pin mapping here # endif # if defined(LOCAL_DEBUG_ATTACH_INTERRUPT) Serial.println(F("Use attachInterrupt for pin=" STR(IR_RECEIVE_PIN))); # endif #else # if defined(LOCAL_DEBUG_ATTACH_INTERRUPT) Serial.println(F("Use static interrupt for pin=" STR(IR_RECEIVE_PIN))); # endif # if defined(USE_INT0) // interrupt on any logical change EICRA |= _BV(ISC00); // clear interrupt bit EIFR |= 1 << INTF0; // enable interrupt on next change EIMSK |= 1 << INT0; # elif defined(USE_INT1) EICRA |= _BV(ISC10); // clear interrupt bit EIFR |= 1 << INTF1; // enable interrupt on next change EIMSK |= 1 << INT1; # elif defined(USE_PCIE) // For ATtiny85 etc. // use PinChangeInterrupt no INT0 for pin PB2 PCMSK = _BV(IR_RECEIVE_PIN); // clear interrupt bit GIFR |= 1 << PCIF; // enable interrupt on next change GIMSK |= 1 << PCIE; # elif defined(USE_PCINT0) PCICR |= _BV(PCIE0); PCMSK0 = digitalPinToBitMask(IR_RECEIVE_PIN); # elif defined(USE_PCINT1) PCICR |= _BV(PCIE1); PCMSK1 = digitalPinToBitMask(IR_RECEIVE_PIN); # elif defined(USE_PCINT2) PCICR |= _BV(PCIE2); PCMSK2 = digitalPinToBitMask(IR_RECEIVE_PIN); # else return false; # endif #endif // defined(USE_ATTACH_INTERRUPT) return true; } void disablePCIInterruptForTinyReceiver() { #if defined(_IR_MEASURE_TIMING) && defined(_IR_TIMING_TEST_PIN) pinModeFast(_IR_TIMING_TEST_PIN, OUTPUT); #endif #if defined(USE_ATTACH_INTERRUPT) || defined(USE_ATTACH_INTERRUPT_DIRECT) # if defined(USE_ATTACH_INTERRUPT) detachInterrupt(digitalPinToInterrupt(IR_RECEIVE_PIN)); # else detachInterrupt(IR_RECEIVE_PIN); # endif #else # if defined(USE_INT0) // clear interrupt bit EIFR |= 1 << INTF0; // disable interrupt on next change EIMSK &= ~(1 << INT0); # elif defined(USE_INT1) // clear interrupt bit EIFR |= 1 << INTF1; // disable interrupt on next change EIMSK &= ~(1 << INT1); # elif defined(USE_PCIE) // For ATtiny85 etc. // clear interrupt bit GIFR |= 1 << PCIF; // disable interrupt on next change GIMSK &= ~(1 << PCIE); # elif defined(USE_PCINT0) PCICR &= ~(_BV(PCIE0)); # elif defined(USE_PCINT1) PCICR &= ~(_BV(PCIE1)); # elif defined(USE_PCINT2) PCICR &= ~(_BV(PCIE2)); # endif #endif // defined(USE_ATTACH_INTERRUPT) } /* * Specify the right INT0, INT1 or PCINT0 interrupt vector according to different pins and cores. * The default value of TINY_RECEIVER_USE_ARDUINO_ATTACH_INTERRUPT is set in TinyIRReceiver.h */ #if !(defined(USE_ATTACH_INTERRUPT) || defined(USE_ATTACH_INTERRUPT_DIRECT)) # if defined(USE_INT0) ISR(INT0_vect) # elif defined(USE_INT1) ISR(INT1_vect) # elif defined(USE_PCIE) // For ATtiny85 etc. // on ATtinyX5 we do not have a INT1_vect but we can use the PCINT0_vect ISR(PCINT0_vect) # elif defined(USE_PCINT0) ISR(PCINT0_vect) # elif defined(USE_PCINT1) ISR(PCINT1_vect) # elif defined(USE_PCINT2) ISR(PCINT2_vect) # else void dummyFunctionToAvoidCompilerErrors() # endif { IRPinChangeInterruptHandler(); } #endif // !(defined(USE_ATTACH_INTERRUPT) || defined(USE_ATTACH_INTERRUPT_DIRECT)) /** @}*/ #if defined(LOCAL_DEBUG_ATTACH_INTERRUPT) #undef LOCAL_DEBUG_ATTACH_INTERRUPT #endif #if defined(LOCAL_TRACE_STATE_MACHINE) #undef LOCAL_TRACE_STATE_MACHINE #endif #if defined(LOCAL_DEBUG) #undef LOCAL_DEBUG #endif #endif // _TINY_IR_RECEIVER_HPP