Skip to content

Commit 13ca5ce

Browse files
committed
restartTimer() now uses variable sMicrosAtLastStopTimer to keep track of uncounted ticks between stopTimer() and restartTimer()
1 parent 0eda1ee commit 13ca5ce

File tree

11 files changed

+109
-73
lines changed

11 files changed

+109
-73
lines changed

README.md

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -518,10 +518,10 @@ void setup() {
518518
}
519519

520520
void loop() {
521-
if (TinyIRReceiverData.justWritten) {
522-
TinyIRReceiverData.justWritten = false;
521+
if (TinyReceiverDecode()) {
523522
printTinyReceiverResultMinimal(&Serial);
524523
}
524+
// No resume() required :-)
525525
}
526526
```
527527

@@ -953,8 +953,11 @@ Since the Arduino `micros()` function has a resolution of 4 µs at 16 MHz,
953953
## Incompatibilities to other libraries and Arduino commands like tone() and analogWrite()
954954
If you use a library which requires the same timer as IRremote, you have a problem, since **the timer resource cannot be shared simultaneously** by both libraries.
955955
956+
### Use NEC protocol and TinyReceiver
957+
[TinyReceiver](https://github.com/Arduino-IRremote/Arduino-IRremote?tab=readme-ov-file#tiny-nec-receiver-and-sender) does not require a timer, it relies on interrupts, thus avoiding any timer resource problems.
958+
956959
### Change timer
957-
The best approach is to change the timer used for IRremote, which can be accomplished by specifying the timer before `#include <IRremote.hpp>`.<br/>
960+
The best approach is to **change the timer** used for IRremote, which can be accomplished by specifying the timer before `#include <IRremote.hpp>`.<br/>
958961
The timer specifications available for your board can be found in [private/IRTimer.hpp](https://github.com/Arduino-IRremote/Arduino-IRremote/blob/master/src/private/IRTimer.hpp).<br/>
959962
960963
```c++
@@ -971,14 +974,20 @@ The timer specifications available for your board can be found in [private/IRTim
971974
Here you see the Arduino Mega board and the available specifications are `IR_USE_AVR_TIMER[1,2,3,4,5]`.<br/>
972975
You **just have to include a line** e.g. `#define IR_USE_AVR_TIMER3` before `#include <IRremote.hpp>` to enable timer 3.
973976

974-
But be aware that the new timer in turn might be incompatible with other libraries or commands.<br/>
975-
For other boards/platforms you must look for the appropriate section guarded by e.g. `#elif defined(ESP32)`.
977+
But be aware that the new timer in turn might be again incompatible with other libraries or Arduino functions.<br/>
978+
For non AVR boards/platforms you must look for the appropriate section guarded by e.g. `#elif defined(ESP32)`.
976979

977980
### Stop and start timer
978981
Another approach can be to share the timer **sequentially** if their functionality is used only for a short period of time like for the **Arduino tone() command**.
979-
An example can be seen [here](https://github.com/Arduino-IRremote/Arduino-IRremote/blob/21b5747a58e9d47c9e3f1beb056d58c875a92b47/examples/ReceiveDemo/ReceiveDemo.ino#L159-L169), where the timer settings for IR receive are restored after the tone has stopped.
980-
For this we must call `IrReceiver.restartTimer()` or better `IrReceiver.restartTimer(microsecondsOfToneDuration)`.<br/>
981-
This only works since each call to` tone()` completely initializes the timer 2 used by the `tone()` command.
982+
An example can be seen [here](https://github.com/Arduino-IRremote/Arduino-IRremote/blob/21b5747a58e9d47c9e3f1beb056d58c875a92b47/examples/ReceiveDemo/ReceiveDemo.ino#L159-L169), where the IR timer is restarted after the tone has stopped.
983+
984+
```c++
985+
IrReceiver.stopTimer(); // Stop timer consistently before calling tone() or other functions using the timer resource.
986+
tone(TONE_PIN, 2200, 8);
987+
delay(8);
988+
IrReceiver.restartTimer(); // Restart IR timer after timer resource is no longer blocked.
989+
```
990+
This works on AVR boards like Uno because each call to` tone()` completely initializes the timer 2 used by the `tone()` command.
982991
983992
## Hardware-PWM signal generation for sending
984993
If you define `SEND_PWM_BY_TIMER`, the send PWM signal is forced to be generated by a hardware timer on most platforms.<br/>

changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ The latest version may not be released!
33
See also the commit log at github: https://github.com/Arduino-IRremote/Arduino-IRremote/commits/master
44
# 4.4.1
55
- Support for ESP 3.0 by akellai.
6+
- restartTimer() now uses variable sMicrosAtLastStopTimer to keep track of uncounted ticks between stopTimer() and restartTimer().
7+
- Removed functions addTicksToInternalTickCounter() and addMicrosToInternalTickCounter(), which were added in 4.1.0.
8+
- Version 2.2.0 of TinyIR with new TinyReceiverDecode() function to be used as drop in for IrReceiver.decode().
69

710
# 4.4.0
811
- Using 8 bit raw timing buffer for all timings except frame gap (former rawbuf[0]).

examples/AllProtocolsOnLCD/AllProtocolsOnLCD.ino

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ void loop() {
240240
} else {
241241
// play tone
242242
auto tStartMillis = millis();
243-
IrReceiver.stopTimer();
243+
// IrReceiver.stopTimer(); // Not really required for Uno, but we then should use restartTimer(aMicrosecondsToAddToGapCounter)
244244
tone(TONE_PIN, 2200);
245245

246246
if ((IrReceiver.decodedIRData.protocol == UNKNOWN || digitalRead(DEBUG_BUTTON_PIN) == LOW)
@@ -259,9 +259,7 @@ void loop() {
259259
while ((millis() - tStartMillis) < 5)
260260
;
261261
noTone(TONE_PIN);
262-
263-
// Restore IR timer. millis() - tStartMillis to compensate for stop of receiver. This enables a correct gap measurement.
264-
IrReceiver.restartTimerWithTicksToAdd((millis() - tStartMillis) * (MICROS_IN_ONE_MILLI / MICROS_PER_TICK));
262+
IrReceiver.restartTimer(5000); // Restart IR timer.
265263

266264
#if defined(USE_LCD)
267265
printIRResultOnLCD();
@@ -287,11 +285,11 @@ void loop() {
287285
#if defined(USE_LCD) && defined(ADC_UTILS_ARE_AVAILABLE)
288286
printsVCCVoltageMillivoltOnLCD();
289287
#endif
290-
IrReceiver.stopTimer();
288+
// IrReceiver.stopTimer(); // Not really required for Uno, but we then should use restartTimer(aMicrosecondsToAddToGapCounter)
291289
tone(TONE_PIN, 2200);
292290
delay(50);
293291
noTone(TONE_PIN);
294-
IrReceiver.restartTimerWithTicksToAdd(50 * (MICROS_IN_ONE_MILLI / MICROS_PER_TICK));
292+
IrReceiver.restartTimer(50000);
295293
}
296294

297295
#if defined(USE_LCD) && defined(ADC_UTILS_ARE_AVAILABLE)

examples/ReceiveDemo/ReceiveDemo.ino

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,7 @@ void loop() {
181181
/*
182182
* No overflow here.
183183
* Stop receiver, generate a single beep, print short info and send usage and start receiver again
184-
*****************************************************************************************************/
185-
184+
*/
186185
if ((IrReceiver.decodedIRData.protocol != SONY) && (IrReceiver.decodedIRData.protocol != PULSE_WIDTH)
187186
&& (IrReceiver.decodedIRData.protocol != PULSE_DISTANCE) && (IrReceiver.decodedIRData.protocol != UNKNOWN)
188187
&& digitalRead(DEBUG_BUTTON_PIN) != LOW) {
@@ -256,13 +255,13 @@ void loop() {
256255
*/
257256
void generateTone() {
258257
#if !defined(ESP8266) && !defined(NRF5) // tone on esp8266 works only once, then it disables IrReceiver.restartTimer() / timerConfigForReceive().
259-
# if defined(ESP32) // ESP32 uses another timer for tone()
258+
# if defined(ESP32) // ESP32 uses another timer for tone(), maybe other platforms (not tested yet) too.
260259
tone(TONE_PIN, 2200, 8);
261260
# else
262-
IrReceiver.stopTimer(); // ESP32 uses another timer for tone(), maybe other platforms (not tested yet) too.
261+
IrReceiver.stopTimer(); // Stop timer consistently before calling tone() or other functions using the timer resource.
263262
tone(TONE_PIN, 2200, 8);
264263
delay(8);
265-
IrReceiver.restartTimer(8000); // Restart IR timer. 8000 to compensate for 8 ms stop of receiver. This enables a correct gap measurement.
264+
IrReceiver.restartTimer(); // Restart IR timer after timer resource is no longer blocked.
266265
# endif
267266
#endif
268267
}
@@ -286,7 +285,7 @@ void handleOverflow() {
286285
delay(50);
287286
tone(TONE_PIN, 1100, 10);
288287
delay(50);
289-
IrReceiver.restartTimer(100000); // to compensate for 100 ms stop of receiver. This enables a correct gap measurement.
288+
IrReceiver.restartTimer();
290289
# endif
291290
#endif
292291
}

examples/ReceiveOneAndSendMultiple/ReceiveOneAndSendMultiple.ino

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ void sendSamsungSmartHubMacro(bool aDoSelect) {
222222
delay(200);
223223

224224
#if !defined(ESP32)
225-
IrReceiver.restartTimer(200000); // to compensate for 200 ms stop of receiver. This enables a correct gap measurement.
225+
IrReceiver.restartTimer(); // Restart IR timer.
226226
#endif
227227

228228
Serial.println(F("Wait for \"not supported\" to disappear"));

examples/TinyReceiver/TinyReceiver.ino

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,30 +106,37 @@ void setup() {
106106
}
107107

108108
void loop() {
109-
if (TinyIRReceiverData.justWritten) {
110-
TinyIRReceiverData.justWritten = false;
109+
if (TinyReceiverDecode()) {
110+
111111
#if !defined(USE_FAST_PROTOCOL)
112112
// We have no address at FAST protocol
113113
Serial.print(F("Address=0x"));
114114
Serial.print(TinyIRReceiverData.Address, HEX);
115115
Serial.print(' ');
116116
#endif
117+
117118
Serial.print(F("Command=0x"));
118119
Serial.print(TinyIRReceiverData.Command, HEX);
119120
if (TinyIRReceiverData.Flags == IRDATA_FLAGS_IS_REPEAT) {
120121
Serial.print(F(" Repeat"));
121122
}
122123
if (TinyIRReceiverData.Flags == IRDATA_FLAGS_PARITY_FAILED) {
123124
Serial.print(F(" Parity failed"));
125+
124126
#if !defined(USE_EXTENDED_NEC_PROTOCOL) && !defined(USE_ONKYO_PROTOCOL)
125127
Serial.print(F(", try USE_EXTENDED_NEC_PROTOCOL or USE_ONKYO_PROTOCOL"));
126128
#endif
129+
127130
}
128131
Serial.println();
129132
}
130133
/*
131134
* Put your code here
132135
*/
136+
137+
/*
138+
* No resume() required :-)
139+
*/
133140
}
134141

135142
/*

src/IRReceive.hpp

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ IRrecv IrReceiver;
6262
* The control structure instance
6363
*/
6464
struct irparams_struct irparams; // the irparams instance
65+
unsigned long sMicrosAtLastStopTimer = 0; // Used to adjust TickCounterForISR with uncounted ticks between stopTimer() and restartTimer()
6566

6667
/**
6768
* Instantiate the IRrecv class. Multiple instantiation is not supported.
@@ -116,9 +117,9 @@ IRrecv::IRrecv(uint_fast8_t aReceivePin, uint_fast8_t aFeedbackLEDPin) {
116117
* => Minimal CPU frequency is 4 MHz
117118
*
118119
**********************************************************************************************************************/
120+
#if defined(ESP8266) || defined(ESP32)
119121
#pragma GCC diagnostic push
120122
#pragma GCC diagnostic ignored "-Wvolatile"
121-
#if defined(ESP8266) || defined(ESP32)
122123
IRAM_ATTR
123124
#endif
124125
void IRReceiveTimerInterruptHandler() {
@@ -377,6 +378,10 @@ void IRrecv::restartTimer() {
377378
// Setup for cyclic 50 us interrupt
378379
timerConfigForReceive(); // no interrupts enabled here!
379380
// Timer interrupt is enabled after state machine reset
381+
if (sMicrosAtLastStopTimer != 0) {
382+
irparams.TickCounterForISR += (micros() - sMicrosAtLastStopTimer) / MICROS_PER_TICK; // adjust TickCounterForISR for correct gap value, which is used for repeat detection
383+
sMicrosAtLastStopTimer = 0;
384+
}
380385
timerEnableReceiveInterrupt(); // Enables the receive sample timer interrupt which consumes a small amount of CPU every 50 us.
381386
#ifdef _IR_MEASURE_TIMING
382387
pinModeFast(_IR_TIMING_TEST_PIN, OUTPUT);
@@ -394,33 +399,30 @@ void IRrecv::enableIRIn() {
394399
* We assume, that timer interrupts are disabled here, otherwise it makes no sense to use this functions.
395400
* Therefore we do not need to guard the change of the volatile TickCounterForISR here :-).
396401
* The tick counter value is already at 100 when decode() gets true, because of the 5000 us minimal gap defined in RECORD_GAP_MICROS.
402+
* If TickCounterForISR is not adjusted with the value of the microseconds, the timer was stopped,
403+
* it can happen, that a new IR frame is recognized as a repeat, because the value of RECORD_GAP_MICROS
404+
* was not reached by TickCounterForISR counter before receiving the new IR frame.
397405
* @param aMicrosecondsToAddToGapCounter To compensate for the amount of microseconds the timer was stopped / disabled.
398406
*/
399-
void IRrecv::start(uint32_t aMicrosecondsToAddToGapCounter) {
400-
irparams.TickCounterForISR += aMicrosecondsToAddToGapCounter / MICROS_PER_TICK;
401-
start();
402-
}
403407
void IRrecv::restartTimer(uint32_t aMicrosecondsToAddToGapCounter) {
404408
irparams.TickCounterForISR += aMicrosecondsToAddToGapCounter / MICROS_PER_TICK;
405-
restartTimer();
406-
}
407-
void IRrecv::startWithTicksToAdd(uint16_t aTicksToAddToGapCounter) {
408-
irparams.TickCounterForISR += aTicksToAddToGapCounter;
409-
start();
409+
timerConfigForReceive(); // no interrupts enabled here!
410+
timerEnableReceiveInterrupt(); // Enables the receive sample timer interrupt which consumes a small amount of CPU every 50 us.
411+
#ifdef _IR_MEASURE_TIMING
412+
pinModeFast(_IR_TIMING_TEST_PIN, OUTPUT);
413+
#endif
410414
}
411415
void IRrecv::restartTimerWithTicksToAdd(uint16_t aTicksToAddToGapCounter) {
412416
irparams.TickCounterForISR += aTicksToAddToGapCounter;
413-
restartTimer();
414-
}
415-
416-
void IRrecv::addTicksToInternalTickCounter(uint16_t aTicksToAddToInternalTickCounter) {
417-
irparams.TickCounterForISR += aTicksToAddToInternalTickCounter;
418-
}
419-
420-
void IRrecv::addMicrosToInternalTickCounter(uint16_t aMicrosecondsToAddToInternalTickCounter) {
421-
irparams.TickCounterForISR += aMicrosecondsToAddToInternalTickCounter / MICROS_PER_TICK;
422-
}
417+
timerConfigForReceive(); // no interrupts enabled here!
418+
timerEnableReceiveInterrupt(); // Enables the receive sample timer interrupt which consumes a small amount of CPU every 50 us.
419+
#ifdef _IR_MEASURE_TIMING
420+
pinModeFast(_IR_TIMING_TEST_PIN, OUTPUT);
421+
#endif
422+
}
423+
#if defined(ESP8266) || defined(ESP32)
423424
#pragma GCC diagnostic push
425+
#endif
424426

425427
/**
426428
* Restarts receiver after send. Is a NOP if sending does not require a timer.
@@ -438,8 +440,12 @@ void IRrecv::stop() {
438440
timerDisableReceiveInterrupt();
439441
}
440442

443+
/*
444+
* Stores microseconds of stop, to adjust TickCounterForISR in restartTimer()
445+
*/
441446
void IRrecv::stopTimer() {
442447
timerDisableReceiveInterrupt();
448+
sMicrosAtLastStopTimer = micros();
443449
}
444450
/**
445451
* Alias for stop().

src/IRremoteInt.h

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ typedef uint64_t IRRawDataType;
113113
#define BITS_IN_RAW_DATA_TYPE 64
114114
#endif
115115

116-
/****************************************************
116+
/**********************************************************
117117
* Declarations for the receiver Interrupt Service Routine
118-
****************************************************/
118+
**********************************************************/
119119
// ISR State-Machine : Receiver States
120120
#define IR_REC_STATE_IDLE 0 // Counting the gap time and waiting for the start bit to arrive
121121
#define IR_REC_STATE_MARK 1 // A mark was received and we are counting the duration of it.
@@ -144,6 +144,8 @@ struct irparams_struct {
144144
IRRawbufType rawbuf[RAW_BUFFER_LENGTH]; ///< raw data / tick counts per mark/space. With 8 bit we can only store up to 12.7 ms. First entry is empty to be backwards compatible.
145145
};
146146

147+
extern unsigned long sMicrosAtLastStopTimer; // Used to adjust TickCounterForISR with uncounted ticks between stopTimer() and restartTimer()
148+
147149
#include "IRProtocol.h"
148150

149151
/*
@@ -213,17 +215,13 @@ class IRrecv {
213215
*/
214216
void begin(uint_fast8_t aReceivePin, bool aEnableLEDFeedback = false, uint_fast8_t aFeedbackLEDPin =
215217
USE_DEFAULT_FEEDBACK_LED_PIN);
216-
void restartTimer();
217218
void start();
218219
void enableIRIn(); // alias for start
219-
void start(uint32_t aMicrosecondsToAddToGapCounter);
220+
void restartTimer();
220221
void restartTimer(uint32_t aMicrosecondsToAddToGapCounter);
221-
void startWithTicksToAdd(uint16_t aTicksToAddToGapCounter);
222222
void restartTimerWithTicksToAdd(uint16_t aTicksToAddToGapCounter);
223223
void restartAfterSend();
224224

225-
void addTicksToInternalTickCounter(uint16_t aTicksToAddToInternalTickCounter);
226-
void addMicrosToInternalTickCounter(uint16_t aMicrosecondsToAddToInternalTickCounter);
227225

228226
bool available();
229227
IRData* read(); // returns decoded data

src/TinyIR.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@
3434
* @{
3535
*/
3636

37-
#define VERSION_TINYIR "2.1.0"
37+
#define VERSION_TINYIR "2.2.0"
3838
#define VERSION_TINYIR_MAJOR 2
39-
#define VERSION_TINYIR_MINOR 1
39+
#define VERSION_TINYIR_MINOR 2
4040
#define VERSION_TINYIR_PATCH 0
4141
// The change log is at the bottom of the file
4242

@@ -243,7 +243,7 @@ struct TinyIRReceiverCallbackDataStruct {
243243
uint8_t Command;
244244
#endif
245245
uint8_t Flags; // Bit coded flags. Can contain one of the bits: IRDATA_FLAGS_IS_REPEAT and IRDATA_FLAGS_PARITY_FAILED
246-
bool justWritten; ///< Is set true if new data is available. Used by the main loop, to avoid multiple evaluations of the same IR frame.
246+
bool justWritten; ///< Is set true if new data is available. Used by the main loop / TinyReceiverDecode(), to avoid multiple evaluations of the same IR frame.
247247
};
248248
extern volatile TinyIRReceiverCallbackDataStruct TinyIRReceiverData;
249249

@@ -252,6 +252,7 @@ bool initPCIInterruptForTinyReceiver();
252252
bool enablePCIInterruptForTinyReceiver();
253253
void disablePCIInterruptForTinyReceiver();
254254
bool isTinyReceiverIdle();
255+
bool TinyReceiverDecode();
255256
void printTinyReceiverResultMinimal(Print *aSerial);
256257

257258
void sendFAST(uint8_t aSendPin, uint16_t aCommand, uint_fast8_t aNumberOfRepeats = 0);
@@ -263,6 +264,9 @@ void sendNEC(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_
263264
void sendExtendedNEC(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats = 0, bool aSendNEC2Repeats = false);
264265

265266
/*
267+
* Version 2.2.0 - 7/2024
268+
* - New TinyReceiverDecode() function to be used as drop in for IrReceiver.decode().
269+
*
266270
* Version 2.1.0 - 2/2024
267271
* - New sendExtendedNEC() function and new parameter aSendNEC2Repeats.
268272
*

src/TinyIRReceiver.hpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@
8484
//#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.
8585
//#define USE_FAST_PROTOCOL // Use FAST protocol instead of NEC / ONKYO.
8686
//#define ENABLE_NEC2_REPEATS // Instead of sending / receiving the NEC special repeat code, send / receive the original frame for repeat.
87-
8887
#include "TinyIR.h" // If not defined, it defines IR_RECEIVE_PIN, IR_FEEDBACK_LED_PIN and TINY_RECEIVER_USE_ARDUINO_ATTACH_INTERRUPT
8988

9089
#include "digitalWriteFast.h"
@@ -384,7 +383,7 @@ void IRPinChangeInterruptHandler(void) {
384383
// Here we have 8 bit command
385384
TinyIRReceiverData.Command = TinyIRReceiverControl.IRRawData.UBytes[2];
386385
# else
387-
// Here we have 16 bit command
386+
// Here we have 16 bit command
388387
TinyIRReceiverData.Command = TinyIRReceiverControl.IRRawData.UWord.HighWord;
389388
# endif
390389

@@ -426,6 +425,17 @@ bool isTinyReceiverIdle() {
426425
return (TinyIRReceiverControl.IRReceiverState == IR_RECEIVER_STATE_WAITING_FOR_START_MARK);
427426
}
428427

428+
/*
429+
* Function to be used as drop in for IrReceiver.decode()
430+
*/
431+
bool TinyReceiverDecode() {
432+
bool tJustWritten = TinyIRReceiverData.justWritten;
433+
if (tJustWritten) {
434+
TinyIRReceiverData.justWritten = false;
435+
}
436+
return tJustWritten;
437+
}
438+
429439
/*
430440
* Checks if IR_RECEIVE_PIN is connected and high
431441
* @return true, if IR Receiver is attached

0 commit comments

Comments
 (0)