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 Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote.
17  * This file is also part of IRMP https://github.com/IRMP-org/IRMP.
18  *
19  ************************************************************************************
20  * MIT License
21  *
22  * Copyright (c) 2022-2026 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 //#define NO_LED_SEND_FEEDBACK_CODE // Disables the LED feedback code for receive.
52 //#define IR_FEEDBACK_LED_PIN 12 // Use this, to disable use of LED_BUILTIN definition for IR_FEEDBACK_LED_PIN
53 #include "TinyIR.h" // Defines protocol timings
54 
55 #include "digitalWriteFast.h"
60 #if !defined(IR_SEND_PIN)
61 #warning "IR_SEND_PIN is not defined, so it is set to 3"
62 #define IR_SEND_PIN 3
63 #endif
64 #if !defined(NO_LED_SEND_FEEDBACK_CODE)
65 #define LED_SEND_FEEDBACK_CODE // Resolve the double negative
66 #endif
67 
68 /*
69  * Generate 38 kHz IR signal by bit banging
70  */
71 void sendMark(uint8_t aSendPin, unsigned int aMarkMicros) {
72  unsigned long tStartMicros = micros();
73  unsigned long tNextPeriodEnding = tStartMicros;
74  unsigned long tMicros;
75  do {
76  /*
77  * Generate pulse
78  */
79  noInterrupts(); // do not let interrupts extend the short on period
80  digitalWriteFast(aSendPin, HIGH);
81  delayMicroseconds(8); // 8 us for a 30 % duty cycle for 38 kHz
82  digitalWriteFast(aSendPin, LOW);
83  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)
84 
85  /*
86  * PWM pause timing and end check
87  * Minimal pause duration is 4.3 us
88  */
89  tNextPeriodEnding += 26; // for 38 kHz
90  do {
91  tMicros = micros(); // we have only 4 us resolution for AVR @16MHz
92  /*
93  * Exit the forever loop if aMarkMicros has reached
94  */
95  unsigned int tDeltaMicros = tMicros - tStartMicros;
96 #if defined(__AVR__)
97  // Just getting variables and check for end condition takes minimal 3.8 us
98  if (tDeltaMicros >= aMarkMicros - (112 / (F_CPU / MICROS_IN_ONE_SECOND))) { // To compensate for call duration - 112 is an empirical value
99 #else
100  if (tDeltaMicros >= aMarkMicros) {
101  #endif
102  return;
103  }
104  } while (tMicros < tNextPeriodEnding);
105  } while (true);
106 }
107 
108 /*
109  * Send NEC with 16 bit address and command, even if aCommand < 0x100 (I.E. ONKYO)
110  * @param aAddress - The 16 bit address to send.
111  * @param aCommand - The 16 bit command to send.
112  * @param aNumberOfRepeats - Number of repeats send at a period of 110 ms.
113  * @param aSendNEC2Repeats - Instead of sending the NEC special repeat code, send the original frame for repeat.
114  */
115 void sendONKYO(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats, bool aSendNEC2Repeats) {
116  pinModeFast(aSendPin, OUTPUT);
117 
118 #if !defined(NO_LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
119  pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
120 # if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
121  digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
122 # else
123  digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
124 # endif
125 #endif
126 
127  uint_fast8_t tNumberOfCommands = aNumberOfRepeats + 1;
128  while (tNumberOfCommands > 0) {
129  unsigned long tStartOfFrameMillis = millis();
130 
131  sendMark(aSendPin, NEC_HEADER_MARK);
132  if ((!aSendNEC2Repeats) && (tNumberOfCommands < aNumberOfRepeats + 1)) {
133  // send the NEC special repeat
134  delayMicroseconds (NEC_REPEAT_HEADER_SPACE); // - 2250
135  } else {
136  // send header
137  delayMicroseconds (NEC_HEADER_SPACE);
138  LongUnion tData;
139  tData.UWord.LowWord = aAddress;
140  tData.UWord.HighWord = aCommand;
141  // Send data
142  for (uint_fast8_t i = 0; i < NEC_BITS; ++i) {
143  sendMark(aSendPin, NEC_BIT_MARK); // constant mark length
144  if (tData.ULong & 1) {
145  delayMicroseconds (NEC_ONE_SPACE);
146  } else {
147  delayMicroseconds (NEC_ZERO_SPACE);
148  }
149  tData.ULong >>= 1; // shift command for next bit
150  }
151  } // send stop bit
152  sendMark(aSendPin, NEC_BIT_MARK);
153 
154  tNumberOfCommands--;
155  // skip last delay!
156  if (tNumberOfCommands > 0) {
157  /*
158  * Check and fallback for wrong RepeatPeriodMillis parameter. I.e the repeat period must be greater than each frame duration.
159  */
160  auto tFrameDurationMillis = millis() - tStartOfFrameMillis;
161  if (NEC_REPEAT_PERIOD / 1000 > tFrameDurationMillis) {
162  delay(NEC_REPEAT_PERIOD / 1000 - tFrameDurationMillis);
163  }
164  }
165  }
166 #if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
167  pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
168 # if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
169  digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
170 # else
171  digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
172 # endif
173 #endif
174 }
175 
176 /*
177  * Send NEC with 8 or 16 bit address or command depending on the values of aAddress and aCommand.
178  * @param aAddress - If aAddress < 0x100 send 8 bit address and 8 bit inverted address, else send 16 bit address.
179  * @param aCommand - If aCommand < 0x100 send 8 bit command and 8 bit inverted command, else send 16 bit command.
180  * @param aNumberOfRepeats - Number of repeats send at a period of 110 ms.
181  * @param aSendNEC2Repeats - Instead of sending the NEC special repeat code, send the original frame for repeat.
182  */
183 void sendNECMinimal(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats) {
184  sendNEC(aSendPin, aAddress, aCommand, aNumberOfRepeats); // sendNECMinimal() is deprecated
185 }
186 void sendNEC(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats, bool aSendNEC2Repeats) {
187  pinModeFast(aSendPin, OUTPUT);
188 
189 #if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
190  pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
191 # if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
192  digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
193 # else
194  digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
195 # endif
196 #endif
197 
198  uint_fast8_t tNumberOfCommands = aNumberOfRepeats + 1;
199  while (tNumberOfCommands > 0) {
200  unsigned long tStartOfFrameMillis = millis();
201 
202  sendMark(aSendPin, NEC_HEADER_MARK);
203  if ((!aSendNEC2Repeats) && (tNumberOfCommands < aNumberOfRepeats + 1)) {
204  // send the NEC special repeat
205  delayMicroseconds (NEC_REPEAT_HEADER_SPACE); // - 2250
206  } else {
207  // send header
208  delayMicroseconds (NEC_HEADER_SPACE);
209  LongUnion tData;
210  /*
211  * The compiler is intelligent and removes the code for "(aAddress > 0xFF)" if we are called with an uint8_t address :-).
212  * Using an uint16_t address requires additional 28 bytes program memory.
213  */
214  if (aAddress > 0xFF) {
215  tData.UWord.LowWord = aAddress;
216  } else {
217  tData.UByte.LowByte = aAddress; // LSB first
218  tData.UByte.MidLowByte = ~aAddress;
219  }
220  if (aCommand > 0xFF) {
221  tData.UWord.HighWord = aCommand;
222  } else {
223  tData.UByte.MidHighByte = aCommand;
224  tData.UByte.HighByte = ~aCommand; // LSB first
225  }
226  // Send data
227  for (uint_fast8_t i = 0; i < NEC_BITS; ++i) {
228  sendMark(aSendPin, NEC_BIT_MARK); // constant mark length
229 
230  if (tData.ULong & 1) {
231  delayMicroseconds (NEC_ONE_SPACE);
232  } else {
233  delayMicroseconds (NEC_ZERO_SPACE);
234  }
235  tData.ULong >>= 1; // shift command for next bit
236  }
237  } // send stop bit
238  sendMark(aSendPin, NEC_BIT_MARK);
239 
240  tNumberOfCommands--;
241  // skip last delay!
242  if (tNumberOfCommands > 0) {
243  /*
244  * Check and fallback for wrong RepeatPeriodMillis parameter. I.e the repeat period must be greater than each frame duration.
245  */
246  auto tFrameDurationMillis = millis() - tStartOfFrameMillis;
247  if (NEC_REPEAT_PERIOD / 1000 > tFrameDurationMillis) {
248  delay(NEC_REPEAT_PERIOD / 1000 - tFrameDurationMillis);
249  }
250  }
251  }
252 #if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
253  pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
254 # if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
255  digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
256 # else
257  digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
258 # endif
259 #endif
260 }
261 
262 /*
263  * Send Extended NEC with a forced 16 bit address and 8 or 16 bit command depending on the value of aCommand.
264  * @param aAddress - Send 16 bit address.
265  * @param aCommand - If aCommand < 0x100 send 8 bit command and 8 bit inverted command, else send 16 bit command.
266  * @param aNumberOfRepeats - Number of repeats send at a period of 110 ms.
267  * @param aSendNEC2Repeats - Instead of sending the NEC special repeat code, send the original frame for repeat.
268  */
269 void sendExtendedNEC(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats, bool aSendNEC2Repeats) {
270  pinModeFast(aSendPin, OUTPUT);
271 
272 #if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
273  pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
274 # if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
275  digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
276 # else
277  digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
278 # endif
279 #endif
280 
281  uint_fast8_t tNumberOfCommands = aNumberOfRepeats + 1;
282  while (tNumberOfCommands > 0) {
283  unsigned long tStartOfFrameMillis = millis();
284 
285  sendMark(aSendPin, NEC_HEADER_MARK);
286  if ((!aSendNEC2Repeats) && (tNumberOfCommands < aNumberOfRepeats + 1)) {
287  // send the NEC special repeat
288  delayMicroseconds (NEC_REPEAT_HEADER_SPACE); // - 2250
289  } else {
290  // send header
291  delayMicroseconds (NEC_HEADER_SPACE);
292  LongUnion tData;
293  tData.UWord.LowWord = aAddress;
294  if (aCommand > 0xFF) {
295  tData.UWord.HighWord = aCommand;
296  } else {
297  tData.UByte.MidHighByte = aCommand;
298  tData.UByte.HighByte = ~aCommand; // LSB first
299  }
300  // Send data
301  for (uint_fast8_t i = 0; i < NEC_BITS; ++i) {
302  sendMark(aSendPin, NEC_BIT_MARK); // constant mark length
303 
304  if (tData.ULong & 1) {
305  delayMicroseconds (NEC_ONE_SPACE);
306  } else {
307  delayMicroseconds (NEC_ZERO_SPACE);
308  }
309  tData.ULong >>= 1; // shift command for next bit
310  }
311  } // send stop bit
312  sendMark(aSendPin, NEC_BIT_MARK);
313 
314  tNumberOfCommands--;
315  // skip last delay!
316  if (tNumberOfCommands > 0) {
317  /*
318  * Check and fallback for wrong RepeatPeriodMillis parameter. I.e the repeat period must be greater than each frame duration.
319  */
320  auto tFrameDurationMillis = millis() - tStartOfFrameMillis;
321  if (NEC_REPEAT_PERIOD / 1000 > tFrameDurationMillis) {
322  delay(NEC_REPEAT_PERIOD / 1000 - tFrameDurationMillis);
323  }
324  }
325  }
326 #if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
327  pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
328 # if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
329  digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
330 # else
331  digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
332 # endif
333 #endif
334 }
335 
336 /*
337  * LSB first, send header, command, inverted command and stop bit
338  */
339 void sendFast8BitAndParity(uint8_t aSendPin, uint8_t aCommand, uint_fast8_t aNumberOfRepeats) {
340  sendFAST(aSendPin, aCommand, aNumberOfRepeats);
341 }
342 
343 /*
344  * LSB first, send header, 16 bit command or 8 bit command, inverted command and stop bit
345  */
346 void sendFAST(uint8_t aSendPin, uint16_t aCommand, uint_fast8_t aNumberOfRepeats) {
347  pinModeFast(aSendPin, OUTPUT);
348 
349 #if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
350  pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
351 # if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
352  digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
353 # else
354  digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
355 # endif
356 #endif
357 
358  uint_fast8_t tNumberOfCommands = aNumberOfRepeats + 1;
359  while (tNumberOfCommands > 0) {
360  unsigned long tStartOfFrameMillis = millis();
361 
362  // send header
363  sendMark(aSendPin, FAST_HEADER_MARK);
364  delayMicroseconds(FAST_HEADER_SPACE);
365  uint16_t tData;
366  /*
367  * The compiler is intelligent and removes the code for "(aCommand > 0xFF)" if we are called with an uint8_t command :-).
368  * Using an uint16_t command requires additional 56 bytes program memory.
369  */
370  if (aCommand > 0xFF) {
371  tData = aCommand;
372  } else {
373  tData = aCommand | (((uint8_t) (~aCommand)) << 8); // LSB first
374  }
375  // Send data
376  for (uint_fast8_t i = 0; i < FAST_BITS; ++i) {
377  sendMark(aSendPin, FAST_BIT_MARK); // constant mark length
378 
379  if (tData & 1) {
380  delayMicroseconds(FAST_ONE_SPACE);
381  } else {
382  delayMicroseconds(FAST_ZERO_SPACE);
383  }
384  tData >>= 1; // shift command for next bit
385  }
386  // send stop bit
387  sendMark(aSendPin, FAST_BIT_MARK);
388 
389  tNumberOfCommands--;
390  // skip last delay!
391  if (tNumberOfCommands > 0) {
392  /*
393  * Check and fallback for wrong RepeatPeriodMillis parameter. I.e the repeat period must be greater than each frame duration.
394  */
395  auto tFrameDurationMillis = millis() - tStartOfFrameMillis;
396  if (FAST_REPEAT_PERIOD / 1000 > tFrameDurationMillis) {
397  delay(FAST_REPEAT_PERIOD / 1000 - tFrameDurationMillis);
398  }
399  }
400  }
401 #if defined(LED_SEND_FEEDBACK_CODE) && defined(IR_FEEDBACK_LED_PIN)
402  pinModeFast(IR_FEEDBACK_LED_PIN, OUTPUT);
403 # if defined(FEEDBACK_LED_IS_ACTIVE_LOW)
404  digitalWriteFast(IR_FEEDBACK_LED_PIN, HIGH);
405 # else
406  digitalWriteFast(IR_FEEDBACK_LED_PIN, LOW);
407 # endif
408 #endif
409 }
410 
413 #endif // _TINY_IR_SENDER_HPP
FAST_BITS
#define FAST_BITS
Definition: TinyIR.h:92
NEC_BITS
#define NEC_BITS
Definition: ir_NEC.hpp:95
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:71
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:101
sendNECMinimal
void sendNECMinimal(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats)
Definition: TinyIRSender.hpp:183
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:269
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:339
LongUnion::HighWord
uint16_t HighWord
Definition: LongUnion.h:81
NEC_ONE_SPACE
#define NEC_ONE_SPACE
Definition: ir_NEC.hpp:102
sendFAST
void sendFAST(uint8_t aSendPin, uint16_t aCommand, uint_fast8_t aNumberOfRepeats)
Definition: TinyIRSender.hpp:346
MICROS_IN_ONE_SECOND
#define MICROS_IN_ONE_SECOND
Definition: IRremote.hpp:216
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:103
sendONKYO
void sendONKYO(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats, bool aSendNEC2Repeats)
Definition: TinyIRSender.hpp:115
NEC_HEADER_SPACE
#define NEC_HEADER_SPACE
Definition: ir_NEC.hpp:99
FAST_REPEAT_PERIOD
#define FAST_REPEAT_PERIOD
Definition: TinyIR.h:103
NEC_REPEAT_HEADER_SPACE
#define NEC_REPEAT_HEADER_SPACE
Definition: ir_NEC.hpp:105
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:110
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:98
sendNEC
void sendNEC(uint8_t aSendPin, uint16_t aAddress, uint16_t aCommand, uint_fast8_t aNumberOfRepeats, bool aSendNEC2Repeats)
Definition: TinyIRSender.hpp:186
FAST_ONE_SPACE
#define FAST_ONE_SPACE
Definition: TinyIR.h:97