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