IRremote
TinyIRSender.hpp
Go to the documentation of this file.
1 /*
2  * TinyIRSender.hpp
3  *
4  * Sends IR protocol data of NEC and FAST protocol using bit banging.
5  * NEC is the protocol of most cheap remote controls for Arduino.
6  *
7  * The FAST protocol is a proprietary modified JVC protocol without address, with parity and with a shorter header.
8  * FAST Protocol characteristics:
9  * - Bit timing is like NEC or JVC
10  * - The header is shorter, 3156 vs. 12500
11  * - No address and 16 bit data, interpreted as 8 bit command and 8 bit inverted command,
12  * leading to a fixed protocol length of (6 + (16 * 3) + 1) * 526 = 55 * 526 = 28930 microseconds or 29 ms.
13  * - Repeats are sent as complete frames but in a 50 ms period / with a 21 ms distance.
14  *
15  *
16  * This file is part of IRMP https://github.com/IRMP-org/IRMP.
17  * This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote.
18  *
19  ************************************************************************************
20  * MIT License
21  *
22  * Copyright (c) 2022-2025 Armin Joachimsmeyer
23  *
24  * Permission is hereby granted, free of charge, to any person obtaining a copy
25  * of this software and associated documentation files (the "Software"), to deal
26  * in the Software without restriction, including without limitation the rights
27  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
28  * copies of the Software, and to permit persons to whom the Software is furnished
29  * to do so, subject to the following conditions:
30  *
31  * The above copyright notice and this permission notice shall be included in all
32  * copies or substantial portions of the Software.
33  *
34  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
35  * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
36  * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
37  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
38  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
39  * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
40  *
41  ************************************************************************************
42  */
43 
44 #ifndef _TINY_IR_SENDER_HPP
45 #define _TINY_IR_SENDER_HPP
46 
47 #include <Arduino.h>
48 
49 //#define ENABLE_NEC2_REPEATS // Instead of sending / receiving the NEC special repeat code, send / receive the original frame for repeat.
50 
51 #if defined(DEBUG)
52 #define LOCAL_DEBUG
53 #else
54 //#define LOCAL_DEBUG // This enables debug output only for this file
55 #endif
56 //#define NO_LED_SEND_FEEDBACK_CODE // Disables the LED feedback code for receive.
57 //#define IR_FEEDBACK_LED_PIN 12 // Use this, to disable use of LED_BUILTIN definition for IR_FEEDBACK_LED_PIN
58 #include "TinyIR.h" // Defines protocol timings
59 
60 #include "digitalWriteFast.h"
65 #if !defined(IR_SEND_PIN)
66 #warning "IR_SEND_PIN is not defined, so it is set to 3"
67 #define IR_SEND_PIN 3
68 #endif
69 #if !defined(NO_LED_SEND_FEEDBACK_CODE)
70 #define LED_SEND_FEEDBACK_CODE // Resolve the double negative
71 #endif
72 
73 /*
74  * Generate 38 kHz IR signal by bit banging
75  */
76 void sendMark(uint8_t aSendPin, unsigned int aMarkMicros) {
77  unsigned long tStartMicros = micros();
78  unsigned long tNextPeriodEnding = tStartMicros;
79  unsigned long tMicros;
80  do {
81  /*
82  * Generate pulse
83  */
84  noInterrupts(); // do not let interrupts extend the short on period
85  digitalWriteFast(aSendPin, HIGH);
86  delayMicroseconds(8); // 8 us for a 30 % duty cycle for 38 kHz
87  digitalWriteFast(aSendPin, LOW);
88  interrupts(); // Enable interrupts - to keep micros correct- for the longer off period 3.4 us until receive ISR is active (for 7 us + pop's)
89 
90  /*
91  * PWM pause timing and end check
92  * Minimal pause duration is 4.3 us
93  */
94  tNextPeriodEnding += 26; // for 38 kHz
95  do {
96  tMicros = micros(); // we have only 4 us resolution for AVR @16MHz
97  /*
98  * Exit the forever loop if aMarkMicros has reached
99  */
100  unsigned int tDeltaMicros = tMicros - tStartMicros;
101 #if defined(__AVR__)
102  // Just getting variables and check for end condition takes minimal 3.8 us
103  if (tDeltaMicros >= aMarkMicros - (112 / (F_CPU / MICROS_IN_ONE_SECOND))) { // To compensate for call duration - 112 is an empirical value
104 #else
105  if (tDeltaMicros >= aMarkMicros) {
106  #endif
107  return;
108  }
109  } while (tMicros < tNextPeriodEnding);
110  } while (true);
111 }
112 
113 /*
114  * Send NEC with 16 bit address and command, even if aCommand < 0x100 (I.E. ONKYO)
115  * @param aAddress - The 16 bit address to send.
116  * @param aCommand - The 16 bit command to send.
117  * @param aNumberOfRepeats - Number of repeats send at a period of 110 ms.
118  * @param aSendNEC2Repeats - Instead of sending the NEC special repeat code, send the original frame for repeat.
119  */
120 void sendONKYO(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats, bool aSendNEC2Repeats) {
121  pinModeFast(aSendPin, OUTPUT);
122 
123 #if !defined(NO_LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
124  pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
125 # if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
126  digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
127 # else
128  digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
129 # endif
130 #endif
131 
132  uint_fast8_t tNumberOfCommands = aNumberOfRepeats + 1;
133  while (tNumberOfCommands > 0) {
134  unsigned long tStartOfFrameMillis = millis();
135 
136  sendMark(aSendPin, NEC_HEADER_MARK);
137  if ((!aSendNEC2Repeats) && (tNumberOfCommands < aNumberOfRepeats + 1)) {
138  // send the NEC special repeat
139  delayMicroseconds (NEC_REPEAT_HEADER_SPACE); // - 2250
140  } else {
141  // send header
142  delayMicroseconds (NEC_HEADER_SPACE);
143  LongUnion tData;
144  tData.UWord.LowWord = aAddress;
145  tData.UWord.HighWord = aCommand;
146  // Send data
147  for (uint_fast8_t i = 0; i < NEC_BITS; ++i) {
148  sendMark(aSendPin, NEC_BIT_MARK); // constant mark length
149  if (tData.ULong & 1) {
150  delayMicroseconds (NEC_ONE_SPACE);
151  } else {
152  delayMicroseconds (NEC_ZERO_SPACE);
153  }
154  tData.ULong >>= 1; // shift command for next bit
155  }
156  } // send stop bit
157  sendMark(aSendPin, NEC_BIT_MARK);
158 
159  tNumberOfCommands--;
160  // skip last delay!
161  if (tNumberOfCommands > 0) {
162  /*
163  * Check and fallback for wrong RepeatPeriodMillis parameter. I.e the repeat period must be greater than each frame duration.
164  */
165  auto tFrameDurationMillis = millis() - tStartOfFrameMillis;
166  if (NEC_REPEAT_PERIOD / 1000 > tFrameDurationMillis) {
167  delay(NEC_REPEAT_PERIOD / 1000 - tFrameDurationMillis);
168  }
169  }
170  }
171 #if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
172  pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
173 # if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
174  digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
175 # else
176  digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
177 # endif
178 #endif
179 }
180 
181 /*
182  * Send NEC with 8 or 16 bit address or command depending on the values of aAddress and aCommand.
183  * @param aAddress - If aAddress < 0x100 send 8 bit address and 8 bit inverted address, else send 16 bit address.
184  * @param aCommand - If aCommand < 0x100 send 8 bit command and 8 bit inverted command, else send 16 bit command.
185  * @param aNumberOfRepeats - Number of repeats send at a period of 110 ms.
186  * @param aSendNEC2Repeats - Instead of sending the NEC special repeat code, send the original frame for repeat.
187  */
188 void sendNECMinimal(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats) {
189  sendNEC(aSendPin, aAddress, aCommand, aNumberOfRepeats); // sendNECMinimal() is deprecated
190 }
191 void sendNEC(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats, bool aSendNEC2Repeats) {
192  pinModeFast(aSendPin, OUTPUT);
193 
194 #if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
195  pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
196 # if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
197  digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
198 # else
199  digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
200 # endif
201 #endif
202 
203  uint_fast8_t tNumberOfCommands = aNumberOfRepeats + 1;
204  while (tNumberOfCommands > 0) {
205  unsigned long tStartOfFrameMillis = millis();
206 
207  sendMark(aSendPin, NEC_HEADER_MARK);
208  if ((!aSendNEC2Repeats) && (tNumberOfCommands < aNumberOfRepeats + 1)) {
209  // send the NEC special repeat
210  delayMicroseconds (NEC_REPEAT_HEADER_SPACE); // - 2250
211  } else {
212  // send header
213  delayMicroseconds (NEC_HEADER_SPACE);
214  LongUnion tData;
215  /*
216  * The compiler is intelligent and removes the code for "(aAddress > 0xFF)" if we are called with an uint8_t address :-).
217  * Using an uint16_t address requires additional 28 bytes program memory.
218  */
219  if (aAddress > 0xFF) {
220  tData.UWord.LowWord = aAddress;
221  } else {
222  tData.UByte.LowByte = aAddress; // LSB first
223  tData.UByte.MidLowByte = ~aAddress;
224  }
225  if (aCommand > 0xFF) {
226  tData.UWord.HighWord = aCommand;
227  } else {
228  tData.UByte.MidHighByte = aCommand;
229  tData.UByte.HighByte = ~aCommand; // LSB first
230  }
231  // Send data
232  for (uint_fast8_t i = 0; i < NEC_BITS; ++i) {
233  sendMark(aSendPin, NEC_BIT_MARK); // constant mark length
234 
235  if (tData.ULong & 1) {
236  delayMicroseconds (NEC_ONE_SPACE);
237  } else {
238  delayMicroseconds (NEC_ZERO_SPACE);
239  }
240  tData.ULong >>= 1; // shift command for next bit
241  }
242  } // send stop bit
243  sendMark(aSendPin, NEC_BIT_MARK);
244 
245  tNumberOfCommands--;
246  // skip last delay!
247  if (tNumberOfCommands > 0) {
248  /*
249  * Check and fallback for wrong RepeatPeriodMillis parameter. I.e the repeat period must be greater than each frame duration.
250  */
251  auto tFrameDurationMillis = millis() - tStartOfFrameMillis;
252  if (NEC_REPEAT_PERIOD / 1000 > tFrameDurationMillis) {
253  delay(NEC_REPEAT_PERIOD / 1000 - tFrameDurationMillis);
254  }
255  }
256  }
257 #if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
258  pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
259 # if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
260  digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
261 # else
262  digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
263 # endif
264 #endif
265 }
266 
267 /*
268  * Send Extended NEC with a forced 16 bit address and 8 or 16 bit command depending on the value of aCommand.
269  * @param aAddress - Send 16 bit address.
270  * @param aCommand - If aCommand < 0x100 send 8 bit command and 8 bit inverted command, else send 16 bit command.
271  * @param aNumberOfRepeats - Number of repeats send at a period of 110 ms.
272  * @param aSendNEC2Repeats - Instead of sending the NEC special repeat code, send the original frame for repeat.
273  */
274 void sendExtendedNEC(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats, bool aSendNEC2Repeats) {
275  pinModeFast(aSendPin, OUTPUT);
276 
277 #if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
278  pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
279 # if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
280  digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
281 # else
282  digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
283 # endif
284 #endif
285 
286  uint_fast8_t tNumberOfCommands = aNumberOfRepeats + 1;
287  while (tNumberOfCommands > 0) {
288  unsigned long tStartOfFrameMillis = millis();
289 
290  sendMark(aSendPin, NEC_HEADER_MARK);
291  if ((!aSendNEC2Repeats) && (tNumberOfCommands < aNumberOfRepeats + 1)) {
292  // send the NEC special repeat
293  delayMicroseconds (NEC_REPEAT_HEADER_SPACE); // - 2250
294  } else {
295  // send header
296  delayMicroseconds (NEC_HEADER_SPACE);
297  LongUnion tData;
298  tData.UWord.LowWord = aAddress;
299  if (aCommand > 0xFF) {
300  tData.UWord.HighWord = aCommand;
301  } else {
302  tData.UByte.MidHighByte = aCommand;
303  tData.UByte.HighByte = ~aCommand; // LSB first
304  }
305  // Send data
306  for (uint_fast8_t i = 0; i < NEC_BITS; ++i) {
307  sendMark(aSendPin, NEC_BIT_MARK); // constant mark length
308 
309  if (tData.ULong & 1) {
310  delayMicroseconds (NEC_ONE_SPACE);
311  } else {
312  delayMicroseconds (NEC_ZERO_SPACE);
313  }
314  tData.ULong >>= 1; // shift command for next bit
315  }
316  } // send stop bit
317  sendMark(aSendPin, NEC_BIT_MARK);
318 
319  tNumberOfCommands--;
320  // skip last delay!
321  if (tNumberOfCommands > 0) {
322  /*
323  * Check and fallback for wrong RepeatPeriodMillis parameter. I.e the repeat period must be greater than each frame duration.
324  */
325  auto tFrameDurationMillis = millis() - tStartOfFrameMillis;
326  if (NEC_REPEAT_PERIOD / 1000 > tFrameDurationMillis) {
327  delay(NEC_REPEAT_PERIOD / 1000 - tFrameDurationMillis);
328  }
329  }
330  }
331 #if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
332  pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
333 # if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
334  digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
335 # else
336  digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
337 # endif
338 #endif
339 }
340 
341 /*
342  * LSB first, send header, command, inverted command and stop bit
343  */
344 void sendFast8BitAndParity(uint8_t aSendPin, uint8_t aCommand, uint_fast8_t aNumberOfRepeats) {
345  sendFAST(aSendPin, aCommand, aNumberOfRepeats);
346 }
347 
348 /*
349  * LSB first, send header, 16 bit command or 8 bit command, inverted command and stop bit
350  */
351 void sendFAST(uint8_t aSendPin, uint16_t aCommand, uint_fast8_t aNumberOfRepeats) {
352  pinModeFast(aSendPin, OUTPUT);
353 
354 #if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
355  pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
356 # if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
357  digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
358 # else
359  digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
360 # endif
361 #endif
362 
363  uint_fast8_t tNumberOfCommands = aNumberOfRepeats + 1;
364  while (tNumberOfCommands > 0) {
365  unsigned long tStartOfFrameMillis = millis();
366 
367  // send header
368  sendMark(aSendPin, FAST_HEADER_MARK);
369  delayMicroseconds(FAST_HEADER_SPACE);
370  uint16_t tData;
371  /*
372  * The compiler is intelligent and removes the code for "(aCommand > 0xFF)" if we are called with an uint8_t command :-).
373  * Using an uint16_t command requires additional 56 bytes program memory.
374  */
375  if (aCommand > 0xFF) {
376  tData = aCommand;
377  } else {
378  tData = aCommand | (((uint8_t) (~aCommand)) << 8); // LSB first
379  }
380  // Send data
381  for (uint_fast8_t i = 0; i < FAST_BITS; ++i) {
382  sendMark(aSendPin, FAST_BIT_MARK); // constant mark length
383 
384  if (tData & 1) {
385  delayMicroseconds(FAST_ONE_SPACE);
386  } else {
387  delayMicroseconds(FAST_ZERO_SPACE);
388  }
389  tData >>= 1; // shift command for next bit
390  }
391  // send stop bit
392  sendMark(aSendPin, FAST_BIT_MARK);
393 
394  tNumberOfCommands--;
395  // skip last delay!
396  if (tNumberOfCommands > 0) {
397  /*
398  * Check and fallback for wrong RepeatPeriodMillis parameter. I.e the repeat period must be greater than each frame duration.
399  */
400  auto tFrameDurationMillis = millis() - tStartOfFrameMillis;
401  if (FAST_REPEAT_PERIOD / 1000 > tFrameDurationMillis) {
402  delay(FAST_REPEAT_PERIOD / 1000 - tFrameDurationMillis);
403  }
404  }
405  }
406 #if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
407  pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
408 # if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
409  digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
410 # else
411  digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
412 # endif
413 #endif
414 }
415 
418 #if defined(LOCAL_DEBUG)
419 #undef LOCAL_DEBUG
420 #endif
421 #endif // _TINY_IR_SENDER_HPP
FAST_BITS
#define FAST_BITS
Definition: TinyIR.h:92
NEC_BITS
#define NEC_BITS
Definition: ir_NEC.hpp:97
LongUnion
Union to specify parts / manifestations of a 32 bit Long without casts and shifts.
Definition: LongUnion.h:59
sendMark
void sendMark(uint8_t aSendPin, unsigned int aMarkMicros)
Definition: TinyIRSender.hpp:76
FAST_BIT_MARK
#define FAST_BIT_MARK
Definition: TinyIR.h:96
pinModeFast
#define pinModeFast
Definition: digitalWriteFast.h:383
LongUnion::UByte
struct LongUnion::@4 UByte
digitalWriteFast
#define digitalWriteFast
Definition: digitalWriteFast.h:347
NEC_BIT_MARK
#define NEC_BIT_MARK
Definition: ir_NEC.hpp:103
sendNECMinimal
void sendNECMinimal(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats)
Definition: TinyIRSender.hpp:188
LongUnion::LowByte
uint8_t LowByte
Definition: LongUnion.h:61
TinyIR.h
LongUnion::HighByte
uint8_t HighByte
Definition: LongUnion.h:64
LongUnion::LowWord
uint16_t LowWord
Definition: LongUnion.h:80
sendExtendedNEC
void sendExtendedNEC(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats, bool aSendNEC2Repeats)
Definition: TinyIRSender.hpp:274
LongUnion::MidLowByte
uint8_t MidLowByte
Definition: LongUnion.h:62
digitalWriteFast.h
sendFast8BitAndParity
void sendFast8BitAndParity(uint8_t aSendPin, uint8_t aCommand, uint_fast8_t aNumberOfRepeats)
Definition: TinyIRSender.hpp:344
LongUnion::HighWord
uint16_t HighWord
Definition: LongUnion.h:81
NEC_ONE_SPACE
#define NEC_ONE_SPACE
Definition: ir_NEC.hpp:104
sendFAST
void sendFAST(uint8_t aSendPin, uint16_t aCommand, uint_fast8_t aNumberOfRepeats)
Definition: TinyIRSender.hpp:351
MICROS_IN_ONE_SECOND
#define MICROS_IN_ONE_SECOND
Definition: IRremote.hpp:211
FAST_ZERO_SPACE
#define FAST_ZERO_SPACE
Definition: TinyIR.h:98
LongUnion::ULong
uint32_t ULong
Definition: LongUnion.h:95
NEC_ZERO_SPACE
#define NEC_ZERO_SPACE
Definition: ir_NEC.hpp:105
sendONKYO
void sendONKYO(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats, bool aSendNEC2Repeats)
Definition: TinyIRSender.hpp:120
NEC_HEADER_SPACE
#define NEC_HEADER_SPACE
Definition: ir_NEC.hpp:101
FAST_REPEAT_PERIOD
#define FAST_REPEAT_PERIOD
Definition: TinyIR.h:103
NEC_REPEAT_HEADER_SPACE
#define NEC_REPEAT_HEADER_SPACE
Definition: ir_NEC.hpp:107
LongUnion::MidHighByte
uint8_t MidHighByte
Definition: LongUnion.h:63
FAST_HEADER_SPACE
#define FAST_HEADER_SPACE
Definition: TinyIR.h:101
NEC_REPEAT_PERIOD
#define NEC_REPEAT_PERIOD
Definition: ir_NEC.hpp:112
LongUnion::UWord
struct LongUnion::@6 UWord
FAST_HEADER_MARK
#define FAST_HEADER_MARK
Definition: TinyIR.h:100
NEC_HEADER_MARK
#define NEC_HEADER_MARK
Definition: ir_NEC.hpp:100
sendNEC
void sendNEC(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats, bool aSendNEC2Repeats)
Definition: TinyIRSender.hpp:191
FAST_ONE_SPACE
#define FAST_ONE_SPACE
Definition: TinyIR.h:97