-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
Copy pathTinyIRReceiver.hpp
720 lines (656 loc) · 29.5 KB
/
TinyIRReceiver.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
/*
* TinyIRReceiver.hpp
*
* Receives IR protocol 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-2024 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. Saves 14 bytes program memory.
* - 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 <Arduino.h>
/*
* 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.
#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
#define LOCAL_TRACE_STATE_MACHINE
#else
//#define LOCAL_TRACE // This enables trace output only for this file
//#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.
*/
//#define IR_RECEIVE_PIN 2
//#define IR_FEEDBACK_LED_PIN LED_BUILTIN
#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(IR_FEEDBACK_LED_PIN) && defined(LED_BUILTIN)
#define IR_FEEDBACK_LED_PIN LED_BUILTIN
#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();
#if defined(LOCAL_DEBUG)
uint32_t sMicrosOfGap; // The length of the gap before the start bit
#endif
/**
* 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_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
digitalWriteFast(IR_FEEDBACK_LED_PIN, !tIRLevel);
#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(LOCAL_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 have 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_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
#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
// 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